CLI Plugins
Kubebuilder CLI plugins wrap scaffolding and CLI features in conveniently packaged Go types that are executed by the
kubebuilder
binary, or any binary that imports them. More specifically, a plugin configures the execution of one
of the following CLI commands:
init
: project initialization.create api
: scaffold Kubernetes API definitions.create webhook
: scaffold Kubernetes webhooks.
Plugins are identified by a key of the form <name>/<version>
. There are two ways to specify a plugin to run:
- Setting
kubebuilder init --plugins=<plugin key>
, which will initialize a project configured for plugin with key<plugin key>
. - A
layout: <plugin key>
in the scaffoldedPROJECT
configuration file. Commands (except forinit
, which scaffolds this file) will look at this value before running to choose which plugin to run.
By default, <plugin key>
will be go.kubebuilder.io/vX
, where X
is some integer.
Plugin interfaces
Each plugin is required to implement the Base
interface.
type Base interface {
// Version returns the plugin's version, which contains a positive integer
// and an optional "stage" string. The string representation of this version
// has format:
// (v)?[1-9][0-9]*(-(alpha|beta))?
Version() Version
// Name returns a DNS1123 label string defining the plugin type.
// For example, Kubebuilder's main plugin would return "go".
//
// Plugin names can be fully-qualified, and non-fully-qualified names are
// prepended to ".kubebuilder.io" to prevent conflicts.
Name() string
// SupportedProjectVersions lists all project configuration versions this
// plugin supports, ex. []string{"2", "3"}. The returned slice cannot be empty.
SupportedProjectVersions() []string
}
Plugin types
On top of being a Base
, a plugin should also implement the GenericSubcommand
interface so it can be
run with a CLI:
type GenericSubcommand interface {
// UpdateContext updates a PluginContext with command-specific help text,
// like description and examples. Can be a no-op if default help text is desired.
UpdateContext(*PluginContext)
// 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(*pflag.FlagSet)
// InjectConfig passes a config to a plugin. The plugin may modify the
// config. Initializing, loading, and saving the config is managed by the
// cli package.
InjectConfig(*config.Config)
// Run runs the subcommand.
Run() error
}
The plugin context is optionally updated by UpdateContext(ctx)
to set custom help text for the target
command; this method can be a no-op, which will preserve the default help text set by the cobra command constructors.
A plugin also implements one of the following interface pairs to declare its support for specific subcommands:
// To implement the 'init' subcommand.
type InitPluginGetter interface {
Base
GetInitPlugin() Init
}
type Init interface {
GenericSubcommand
}
// To implement the 'create api' subcommand.
type CreateAPIPluginGetter interface {
Base
GetCreateAPIPlugin() CreateAPI
}
type CreateAPI interface {
GenericSubcommand
}
// To implement the 'create webhook' subcommand.
type CreateWebhookPluginGetter interface {
Base
GetCreateWebhookPlugin() CreateWebhook
}
type CreateWebhook interface {
GenericSubcommand
}
The pair system allows a plugin implementation to have the same Base
“parent” plugin with child typed plugins. For
example, the following plugin go.example.com/v1
implements each of the above pairs:
import (
"github.com/spf13/pflag"
"sigs.k8s.io/kubebuilder/pkg/model/config"
"sigs.k8s.io/kubebuilder/pkg/plugin"
)
// Plugin embeds internal types that implement one plugin type each,
// while implementing `Base` itself.
type Plugin struct {
initPlugin
createAPIPlugin
createWebhookPlugin
}
// Base implementation.
func (Plugin) Name() string { return "go.example.com" }
func (Plugin) Version() plugin.Version { return plugin.Version{Number: 1} }
func (Plugin) SupportedProjectVersions() []string { return []string{"3"} }
// Getters.
func (p Plugin) GetInitPlugin() plugin.Init { return &p.initPlugin }
func (p Plugin) GetCreateAPIPlugin() plugin.CreateAPI { return &p.createAPIPlugin }
func (p Plugin) GetCreateWebhookPlugin() plugin.CreateWebhook { return &p.createWebhookPlugin }
// Example of one of the internal implementations of GenericSubcommand.
type createAPIPlugin struct { ... }
func (p createAPIPlugin) UpdateContext(ctx *plugin.Context) { ... }
func (p *createAPIPlugin) BindFlags(fs *pflag.FlagSet) { ... }
func (p *createAPIPlugin) InjectConfig(c *config.Config) { ... }
func (p *createAPIPlugin) Run() error { ... }
For a full implementation example, check out Kubebuilder’s native go.kubebuilder.io
plugin.
Deprecated Plugins
Once a plugin is deprecated, have it implement a Deprecated
interface so a deprecation warning will be printed when it is used:
// 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 {
// DeprecationWarning returns a deprecation message that callers
// can use to warn users of deprecations
DeprecationWarning() string
}
CLI system
Plugins are run using a CLI
object, which maps a plugin type to a subcommand and calls that plugin’s methods.
For example, writing a program that injects an Init
plugin into a CLI
then calling CLI.Run()
will call the
plugin’s UpdateContext
, BindFlags
, InjectConfig
, and Run
methods with information a user has passed to the
program in kubebuilder init
. Hopefully the following code example will clarify this rather confusing description:
// Several plugins for different languages with different versions.
import (
ansiblev1 "github.com/example/my-plugins/pkg/plugins/ansible/v1"
golangv1 "github.com/example/my-plugins/pkg/plugins/golang/v1" // From the above example.
golangv2 "github.com/example/my-plugins/pkg/plugins/golang/v2"
helmv1 "github.com/example/my-plugins/pkg/plugins/helm/v1"
)
// Create a CLI with name 'controller-builder' that supports
// project version "3" and Go, Helm, and Ansible plugins
// of various versions. This CLI defaults to running the latest
// Go plugin version unless '--plugins' or 'layout' specify
// one of the other plugins.
func main() {
c, err := cli.New(
cli.WithCommandName("controller-builder"),
cli.WithDefaultProjectVersion("3"),
cli.WithExtraCommands(newCustomCobraCmd()),
cli.WithPlugins(
&golangv1.Plugin{}, // "go.example.com/v1"
&golangv2.Plugin{}, // "go.example.com/v2-alpha"
&helmv1.Plugin{}, // "helm.example.com/v1"
&ansiblev1.Plugin{}, // "ansible.example.com/v1"
),
cli.WithDefaultPlugins(
&golangv1.Plugin{}, // "go.example.com/v1", default
),
)
if err != nil {
log.Fatal(err)
}
if err := c.Run(); err != nil {
log.Fatal(err)
}
}
This program can then be built and run in the following ways:
Default behavior:
# Initialize a project with the default Init plugin, "go.example.com/v1".
# This key is automatically written to a PROJECT config file.
$ controller-builder init
# Create an API and webhook with "go.example.com/v1" CreateAPI and
# CreateWebhook plugin methods. This key was read from the config file.
$ controller-builder create api [flags]
$ controller-builder create webhook [flags]
Selecting a plugin using --plugins
:
# Initialize a project with the "ansible.example.com/v1" Init plugin.
# Like above, this key is written to a config file.
$ controller-builder init --plugins ansible
# Create an API and webhook with "ansible.example.com/v1" CreateAPI
# and CreateWebhook plugin methods. This key was read from the config file.
$ controller-builder create api [flags]
$ controller-builder create webhook [flags]
Plugin naming
Plugin names must be DNS1123 labels and should be fully qualified, i.e. they have a suffix like
.example.com
. For example, the base Go scaffold used with kubebuilder
commands has name go.kubebuilder.io
.
Qualified names prevent conflicts between plugin names; both go.kubebuilder.io
and go.example.com
can both scaffold
Go code and can be specified by a user.
Plugin versioning
A plugin’s Version()
method returns a plugin.Version
object containing an integer value
and optionally a stage string of either “alpha” or “beta”. The integer denotes the current version of a plugin.
Two different integer values between versions of plugins indicate that the two plugins are incompatible. The stage
string denotes plugin stability:
alpha
should be used for plugins that are frequently changed and may break between uses.beta
should be used for plugins that are only changed in minor ways, ex. bug fixes.
Breaking changes
Any change that will break a project scaffolded by the previous plugin version is a breaking change.