New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
designs: extensible CLI and scaffolding plugins #1250
designs: extensible CLI and scaffolding plugins #1250
Conversation
To answer @DirectXMan12's questions from #1249
In the prototype, the Basically, the plugin is loaded during
The prototype doesn't really get into this, but I agree it is something we should discuss and cover. Open to ideas on this. I can think of three options:
I like this idea. We would need a way to know which config struct to parse to build the flag set for each command. For example, this seems like it implies that the Go V2 scaffolding would need 3 config structs, one for each subcommand it implements (e.g.
I'll answer your question in two ways:
|
type Plugin interface { | ||
// Version is the project version that this plugin implements. | ||
// For example, Kubebuilder's Go v2 plugin implementation would return "2" | ||
Version() string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about we have here the []types as well? (ansible, helm, go)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we probably want like name and version here, unless we want to automatically deduce name from elsewhere, but that seems 🤢
That way we get kubebuilder:v2
or ansible:v3
or osdk:v1
and such.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking that kubebuilder or sdk are equals when is the go type, so I mean it doesn't really matter since the scaffold will be the same and what may change are the top customizations. Because of this, in my mind types would represent the language used. But may I am unable to get some point.
WDYT about we have type=[go|helm|ansible] and builder=[sdk|kube]?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Things we seem to need from the base Plugin
interface:
- Method to get a version string (
Version()
)- Always an integer as string.
- Method to get an identifier for a plugin (
Name()
)- Should this identifier format be up the implementer, or be
name
orname:vX
?
- Should this identifier format be up the implementer, or be
If we go with a self-specified identifier, do we care about prevent naming conflicts between plugins?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on name and version
We should not force folks to determine if they are are type or a builder, a name for the given plugin that is being used and a version of that plugin will give us the flexibility we need IMO.
if Version
is always an int, then can we make it an unsigned int type to get compile-time guarantees?
I think I'd kinda lean towards this or option 2. It's a lot more flexible, makes it easier to do stuff like "I need project or environment specific tweaks to some base scaffolding" (e.g. I want Bazel build rules set up automatically). Re project file (cc @camilamacedo86's comment above), I'd imagine something like: domain: test.kubebuilder.io
repo: sigs.k8s.io/kubebuilder/testdata/project-v3
version: "3x" # version strings are mostly meaningless at this point, but the x is a nice marker that we are using plugins
resources:
- group: crew
kind: Captain
version: v1
model:
# these are persistent plugins -- they'll be automatically invoked
# for additional commands that they support, unless otherwise disabled
- { name: "kubebuilder", version: "2" } # "root" plugin -- gets an empty world tree
- name: "add-bazel"
version: "1"
config: { pruneMakefile: true } # custom config |
I'd loosely imagine that corresponds to $ kubebuilder init --domain=test.kubebuilder.io --scaffold=kubebuilder:v2,add-bazel:v1 --add-bazel-prune-makefile=true
$ kubebuilder create api --group crew --kind Captain --version v1 # automatically gets bazel files
For ease of use, we'd probably want to do the following:
|
So, for example, an osdk operator might look use or internally in the osdk command (pulling from your example): func main() {
c, err := cli.New(
cli.WithCommandName("operator-sdk"),
cli.WithDefaultModel(&golangv2.Plugin{}, &osdkmods.Plugin{}),
cli.WithExtraCommands(newCustomCobraCmd()),
cli.WithPlugins(
&golangv1.Plugin{},
&golangv2.Plugin{},
&helmv1.Plugin{},
&ansiblev1.Plugin{},
),
)
if err != nil {
log.Fatal(err)
}
if err := c.Run(); err != nil {
log.Fatal(err)
}
} |
type Plugin interface { | ||
// Version is the project version that this plugin implements. | ||
// For example, Kubebuilder's Go v2 plugin implementation would return "2" | ||
Version() string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we probably want like name and version here, unless we want to automatically deduce name from elsewhere, but that seems 🤢
That way we get kubebuilder:v2
or ansible:v3
or osdk:v1
and such.
// Deprecated is an interface that, if implemented, informs the CLI | ||
// that the plugin is deprecated. The CLI uses this to print deprecation | ||
// warnings when the plugin is in use. | ||
type Deprecated interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 :-)
// BindInitFlags binds the plugin's init flags to the CLI. This allows each | ||
// plugin to define its own command line flags for the `kubebuilder init` | ||
// subcommand. | ||
BindInitFlags(fs *pflag.FlagSet) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I kinda don't want to suggest this, but I figured I throw it out: all the CLI arg logic from CT is in its own package, and can be used with arbitrary CLI markers. It's used in a couple of the helper commands. but it completely ignores existing UX for CLI programs, so we probably don't want to use it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the benefit of doing this vs. using pflag.FlagSet
and the plugin are responsible for adding its own flags and the types that it uses internally for the flags.
I am having a tough time seeing the benefit of that approach vs this approach which feels pretty common to me?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess not exposing pflag
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could wrap *pflag.FlagSet
in a PluginFlagSet
struct so we can change the underlying flagset implementation later while keeping the interface forward-compatible:
type PluginFlagSet struct {
*pflag.FlagSet
}
type InitPlugin interface {
...
BindInitFlags(PluginFlagSet)
}
That way we can deprecate pflag
by using a conversion function to transform *pflag.FlagSet
to a new flagset type until we remove support entirely. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the result of all this back and forth might just be that avoiding pflag (and having cobra in the interface) is more work than it's worth ATM. Sorry for starting all that. Let's just go forward with pflag for now, and we can always re-adjust a bit later if we really need to.
|
||
With all of that said, even this exposure of `cobra.Command` could be problematic. If Kubebuilder decides in the future to transition to a different CLI framework (or to roll its own) it has to either continue maintaining support for these extra cobra commands passed into it, or it was to break the CLI API. | ||
|
||
Are there other ideas for how to handle the following requirements? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As much as I don't want to expose cobra, I kinda feel like if we try we might end up re-implementing it poorly. As a thought exercise:
What do we need/want?
- Cohesive
--help
with additional subcommands listed based on the--scaffold
flag and the persistent plugins - Additional subcommands that basically have all args and unknown flags passed to them
- (nice to have from a UX perspective) common flags may be specified anywhere on the command line, not just before the subcommand
anything I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's going to be hard to avoid exposing cobra
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a look, have a few comments.
|
||
## Overview | ||
I would like for Kubebuilder to become more extensible, such that it could be imported and used as a library in other projects. Specifically, I'm looking for a way to use Kubebuilder's existing CLI and scaffolding for Go projects, but to also be able to augment Kubebuilder's project versions with other custom project versions so that I can support the Kubebuilder workflow with non-Go operators (e.g. operator-sdk's Ansible and Helm-based operators). | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use version for project versioning e.g. 1, 2. For this use-case, we can use the term types
to refer different types of Operator projects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, I has to re-read from the start once I understood that version was not refering to a version but to a type
// For example, Kubebuilder's Go v2 plugin implementation would return "2" | ||
Version() string | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better would be call this method Name
instead of Version
. Version
indicates the version of the plugin itself that could v1, v2,....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see my comment at #1250 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also lean to both Name() string
and Version() string
// Init initializes a project. | ||
Init() error | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if I have a plugin that implements InitPlugin
interface, does it override the default init behavior or does it augment the default behavior ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to #1250 (comment) it seems you queue Init commands using kubebuilder Init as the root.
# Extensible CLI and Scaffolding Plugins | ||
|
||
## Overview | ||
I would like for Kubebuilder to become more extensible, such that it could be imported and used as a library in other projects. Specifically, I'm looking for a way to use Kubebuilder's existing CLI and scaffolding for Go projects, but to also be able to augment Kubebuilder's project versions with other custom project versions so that I can support the Kubebuilder workflow with non-Go operators (e.g. operator-sdk's Ansible and Helm-based operators). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One observation: I think the proposal is mixing extensibility
with using KB CLI as library
. I am wondering if decoupling them will simplify the proposal (if possible).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me these things go hand in hand, since using KB CLI as a library
by itself is easy to do (CLI option structs like cmd.apiOptions
could be exported) while making KB extensible
in the manner suggested gives you a CLI library "for free". Does that make sense @droot?
|
||
// Init initializes a project. | ||
Init() error | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One high level pattern is -- There will be metadata associated with the project (ex. domain, groups...) and type (helm
, golang
...), we need a way to persist that data in probably PROJECT
file and each subcommand invocation can be provided with that metadata and can be written back at the end during the invocation or at the end.
If we can come up with a generic metadata interface (Generic Key-Value may be ?) on PROJECT file, then we can decouple commandline args from the plugins.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this sounds like a necessity. I like @DirectXMan12's suggestion from above. Is this along the lines of what you're looking for?
|
||
With all of that said, even this exposure of `cobra.Command` could be problematic. If Kubebuilder decides in the future to transition to a different CLI framework (or to roll its own) it has to either continue maintaining support for these extra cobra commands passed into it, or it was to break the CLI API. | ||
|
||
Are there other ideas for how to handle the following requirements? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's going to be hard to avoid exposing cobra
.
I have an idea that seems to address these 3 requirements. We have a "root" plugin that will dump some metadata in PROJECT file during domain: test.kubebuilder.io
repo: sigs.k8s.io/kubebuilder/testdata/project-v3
version: "3x" # version strings are mostly meaningless at this point, but the x is a nice marker that we are using plugins
resources:
- group: crew
kind: Captain
version: v1
clis:
- name: helm
type: subcommand
path: / # "/" means this subcommand is added at the root of the kubebuilder command
cmd: ["osdk", "helm", "other-stuff"]
config: { pruneMakefile: true } # custom config
- name: "add-bazel"
type: plugin
path: /create/api # this plugin will be executed with "kubebuilder create api" command
cmd: ["add-bazel", "other-stuff"]
config: { pruneMakefile: true } # custom config We distinguish Each time kubebuilder executes, it loads PROJECT file. If the type is In terms of help text integration, kubebuilder exec the subcommand e.g. |
@mengqiy that doesn't necessarily give us cohesive help though, or command line parsing. We'd have to treat options like git does (flags for parent commands come before the subcommand, anything after goes to the child command), and we wouldn't get combined help. Plus, I'm not certain we want to persist optional subcommands to the project file -- that feels weird to me. At any rate, for the subcommand issue, let's say for now subcommands only exist when KB is being used as a library, ignoring the project file, and figure out how to deal with it there. |
Point of note: we don't currently use any persistent flags in kubebuilder. Meaning maybe persistent flag parsing is probably not much of an issue. We just need cohesive help. |
``` | ||
|
||
### Optional | ||
Next, a plugin could optionally implement further interfaces to declare its support for specific Kubebuilder subcommands. For example: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be achievable without specific subcommand interfaces? Having specific interfaces for Init
, CreateAPI
, ... would limit the subcommands to these list and would require modifying the source when any other subcommand wants to be added. What about the following:
type Plugin interface {
// Name is the name of this plugin.
// For example, Kubebuilder's Go v2 plugin implementation would return "kubebuilder"
Name() string
// Version is the project version that this plugin implements.
// For example, Kubebuilder's Go v2 plugin implementation would return "2"
Version() string
// Subcommands returns the different subcommands that this plugin implements.
Subcommands() []Subcommand
}
type Subcommand struct {
// Command returns the subcommand that this type is implementing.
// For example, `kubebuilder create api` subcommand implementation would return `[]string{"create", "api"}`
Command() []string
// Description returns a description of what this subcommand does. It is used to display help.
Description() string
// Example returns one or more examples of the command-line usage
// of this plugin's project subcommand support. It is used to display help.
Example() string
// BindFlags binds the plugin's flags to the CLI. This allows each plugin to define its own
// command line flags for the kubebuilder subcommand.
BindFlags(fs *pflag.FlagSet)
// Run runs the subcommand.
Run() error
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given how plugin interfaces are lain out in the current POC, I'd say this is a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we went with the above approach, I would think that Subcommand may need optional Subcommands. I don't like the idea of having to understand "create", "API" -> create API. Reading this into a CLI parser would be difficult right?
I think my vote would be for having specific interfaces for specific commands.
- If we run into a case where it makes sense to pass more info around, we would need to make this generic to fit the above model. This could lead to lots of confusion in the future IMO.
- Being explicit about when I want my plugin to run if I am choosing an override means that if and when kubebuilder adds a new command, I am not accidentally overriding/layering it in my plugin, with a generic subcommand. I think that this is a good thing.
- Marking deprecation and changes to a plugin interface will be easier if we have concrete types rather than implementations of a generic interface IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we went with the above approach, I would think that Subcommand may need optional Subcommands. I don't like the idea of having to understand "create", "API" -> create API. Reading this into a CLI parser would be difficult right?
Subcommand.Command
is a []string
. kubebuilder create api
would return []string{"create", "api"}
. The recursive approach could also be done by adding a Subcommands() []Subcommand
and changing Command string
but I usually tend to avoid recursion.
- If we run into a case where it makes sense to pass more info around, we would need to make this generic to fit the above model. This could lead to lots of confusion in the future IMO.
The info is passed through the flags, the commands layer has no info to pass around. Could you think of any example?
- Being explicit about when I want my plugin to run if I am choosing an override means that if and when kubebuilder adds a new command, I am not accidentally overriding/layering it in my plugin, with a generic subcommand. I think that this is a good thing.
The counter part is that no extension could develop any command that kubebuilder doesn't offer as a Plugin interface, while the geenric approach would allow it.
- Marking deprecation and changes to a plugin interface will be easier if we have concrete types rather than implementations of a generic interface IMO.
The Deprecated
interface could still be implemented and checked with generic interface, which difficulty are you seeing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The info is passed through the flags, the commands layer has no info to pass around. Could you think of any example?
There might be other metadata or state to inject. For example, configuration from the PROJECT file, plugin configuration, environment info, etc. I suppose that could also be defined similar to the Deprecated
interface, where the CLI checks if the subcommand implements some injection function(s).
The counter part is that no extension could develop any command that kubebuilder doesn't offer as a Plugin interface, while the geenric approach would allow it.
The WithExtraCommands()
functional option to cli.New
would allow other binaries to implement their own subcommands, with the caveat that they wouldn't be allowed to override subcommands defined and used by Kubebuilder. So init
and create
would have well-defined subcommands and interfaces for customizing their logic, but they would not allow plugins to define other custom subcommands.
Using Operator SDK as an example, it would be possible to create a new subcommand that provides integrations with OLM and pass it into the CLI with cli.New(cli.WithExtraCommands(olmCmd))
IMO, preventing open-ended subcommand building in Kubebuilder's built-in subcommand trees would be a feature that ensure's that the Kubebuilder-defined operator development workflow is consistent across different extensions/binaries.
Another potential pain point with this approach is that it sort of locks Kubebuilder into this interface for all subcommands. I can't think of a great example at the moment, but I could envision a requirement where maybe the init
subcommand interface needs to have an extra required method on its plugin interface.
Perhaps a middle ground would be something like this:
type Plugin interface { ... }
type InitPluginGetter interface {
Plugin
GetInitPlugin() InitPlugin
}
type InitPlugin interface {
GenericSubcommand
}
type GenericSubcommand interface {
// Description returns a description of what this subcommand does. It is used to display help.
Description() string
// Example returns one or more examples of the command-line usage
// of this plugin's project subcommand support. It is used to display help.
Example() string
// BindFlags binds the plugin's flags to the CLI. This allows each plugin to define its own
// command line flags for the kubebuilder subcommand.
BindFlags(fs *pflag.FlagSet)
// Run runs the subcommand.
Run() error
}
In this case, we would still have explicit subcommands and type checking, most subcommand plugins could use the GenericSubcommand
interface, but it would give us some flexibility in the future to allow changing individual plugin definitions, while still retaining compiler enforced type checking/safety on Kubebuilder's subcommands.
It doesn't have to be persisted in the |
One concern I have with this approach is that letting the user specify the plugin version may confuse the users and the users may do the wrong things. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to explore the custom config in the project file and where that maps in the interface for a plugin. Unless I am misunderstanding how that would worK?
If each plugin has multiple versions, 2+ plugins can have multiple combinations, but only a few of them are supposed to work.
You are correct, is there a way to determine this by the plugin saying the name/version that it is layered on top of? The questions becomes kubebuilder:v2,osdk:v2,add-bazel:v1
. add-bazel would not know about osdk:v2`. I think we could do something clever like "does it layer the root, if it does then we assume it is fine. If it does not, does it layer the next layer if it does great, if not....." WDYT?
This would allow the CLI time check to with some level of knowledge and allow for composition that we may not have expected?
type Plugin interface { | ||
// Version is the project version that this plugin implements. | ||
// For example, Kubebuilder's Go v2 plugin implementation would return "2" | ||
Version() string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on name and version
We should not force folks to determine if they are are type or a builder, a name for the given plugin that is being used and a version of that plugin will give us the flexibility we need IMO.
if Version
is always an int, then can we make it an unsigned int type to get compile-time guarantees?
``` | ||
|
||
### Optional | ||
Next, a plugin could optionally implement further interfaces to declare its support for specific Kubebuilder subcommands. For example: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we went with the above approach, I would think that Subcommand may need optional Subcommands. I don't like the idea of having to understand "create", "API" -> create API. Reading this into a CLI parser would be difficult right?
I think my vote would be for having specific interfaces for specific commands.
- If we run into a case where it makes sense to pass more info around, we would need to make this generic to fit the above model. This could lead to lots of confusion in the future IMO.
- Being explicit about when I want my plugin to run if I am choosing an override means that if and when kubebuilder adds a new command, I am not accidentally overriding/layering it in my plugin, with a generic subcommand. I think that this is a good thing.
- Marking deprecation and changes to a plugin interface will be easier if we have concrete types rather than implementations of a generic interface IMO.
// BindInitFlags binds the plugin's init flags to the CLI. This allows each | ||
// plugin to define its own command line flags for the `kubebuilder init` | ||
// subcommand. | ||
BindInitFlags(fs *pflag.FlagSet) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would be the benefit of doing this vs. using pflag.FlagSet
and the plugin are responsible for adding its own flags and the types that it uses internally for the flags.
I am having a tough time seeing the benefit of that approach vs this approach which feels pretty common to me?
I think something like this is a requirement for a fully-implemented and configurable plugin system. However we may be able to separate that discussion from how we want to write the I hacked together a branch implementing the proposal as-is (no CLI changes though). Take a look and post what you do/don't like: https://github.com/estroz/kubebuilder/tree/feature/plugins
This layering logic seems feasible. Perhaps we can add a
However like I mentioned above, perhaps we should push this layering off into a follow-up proposal since it needs more discussion and it seems like we can mostly implement the underlying interfaces beforehand. |
Version() string | ||
// Version returns the project version that this plugin implements. | ||
// For example, Kubebuilder's Go v2 plugin implementation would return 2. | ||
Version() uint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should keep this as Version() string
as that will allow more flexible versioning formats as 2.1.0
or 2.3-alpha1
similar to api version names-
Created https://github.com/kubernetes-sigs/kubebuilder/tree/feature/plugins-part-2-electric-boogaloo to collaborate |
doing tentative merge on this design doc. Will continue updating as we develop the feature branch. /lgtm |
/hold @joelanford @estroz can one of you update this with the latest details and we'll get it merged |
Name() string | ||
// SupportedProjectVersions lists all project configuration versions this | ||
// plugin supports, ex. []string{"2", "3"}. The returned slice cannot be empty. | ||
SupportedProjectVersions() []string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After looking over our discussion notes, I realized that Plugin.Version()
and a PROJECT
file's version
field can/should drift since the former describes a plugin's development cycle while the latter describes the overall project's. Furthermore, indexing the CLI's plugins, which are indexed by semantic version, with a single digit version string (version: "2"
) will not work, especially due to the development cycle disconnect described above.
The SupportedProjectVersions()
allow a plugin to be added to the CLI's plugins such that they can be indexed by project version from PROJECT
/--project-version
:
func WithPlugins(plugins ...plugin.Base) Option {
return func(c *cli) error {
for _, p := range plugins {
for _, ver := range p.SupportedProjectVersions() {
if _, ok := c.plugins[ver]; !ok {
c.plugins[ver] = []plugin.Base{}
}
c.plugins[ver] = append(c.plugins[ver], p)
}
}
return nil
}
}
@DirectXMan12 @mengqiy @joelanford @Adirio do my concerns hold water, and if so does this solution make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 seems fine to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So basically each plugin will have a method that returns which versions it should be added to. That sounds ok to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is ok for me.
Waiting for other oks in order to flag it and we see it merged.
/lgtm /hold cancel 🎉 |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: DirectXMan12, joelanford The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
feature: implement plugin proposal in #1250
Description of the change
This PR adds a design proposal for Kubebuilder CLI and scaffolding extensibility
Motivation for the change
To achieve agreement on a way forward for Kubebuilder CLI and scaffolding extensibility so that the Kubernetes controller/operator communities can align on Go project layouts and enable extension points for other customizations.