扩展命令行界面功能和插件

Kubebuilder 提供了一种可扩展的架构,用于使用插件搭建项目。这些插件允许您自定义 CLI 行为或集成新功能。

在本指南中,我们将探讨如何扩展 CLI 功能、创建自定义插件以及打包多个插件。

创建自定义插件

要创建自定义插件,您需要实现 Kubebuilder 插件接口

此接口允许您的插件挂钩到 Kubebuilder 的命令(initcreate apicreate webhook 等),并添加自定义逻辑。

自定义插件示例

您可以创建一个插件,它生成特定语言的框架和所需的配置文件,使用 捆绑插件。此示例演示如何将 Go 语言插件与 Kustomize 插件结合使用:

import (
    kustomizecommonv2 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2"
    golangv4 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4"
)

mylanguagev1Bundle, _ := plugin.NewBundle(
    plugin.WithName("mylanguage.kubebuilder.io"),
    plugin.WithVersion(plugin.Version{Number: 1}),
    plugin.WithPlugins(kustomizecommonv2.Plugin{}, mylanguagev1.Plugin{}),
)

这个结构使您能够通过 Kustomize 建立一个通用的配置基础,并通过 mylanguagev1 处理特定于语言的文件。

您还可以使用您的插件来构建特定的资源,例如 CRD 和控制器,使用 create apicreate webhook 子命令。

插件子命令

插件负责实现当子命令被调用时将执行的代码。您可以通过实现 插件接口 来创建一个新的插件。

除了作为 Base 之外,插件还应实现 SubcommandMetadata 接口,以便可以通过 CLI 运行。可选地,可以为目标命令设置自定义帮助文本;此方法可以是无操作的,这将保留由 cobra 命令构造函数设置的默认帮助文本。

Kubebuilder CLI 插件将脚手架和 CLI 功能包装在方便的 Go 类型中,这些类型由 kubebuilder 二进制文件或任何导入它们的二进制文件执行。更具体地说,一个插件配置以下 CLI 命令之一的执行:

  • init:初始化项目结构。
  • create api:生成一个新的 API 和控制器。
  • 创建 webhook: 建立一个新的 webhook。
  • 编辑: 编辑项目结构。

以下是使用 init 子命令与自定义插件的示例:

kubebuilder init --plugins=mylanguage.kubebuilder.io/v1

这将使用 mylanguage 插件初始化一个项目。

插件密钥

插件通过 <name>/<version> 形式的键来识别。指定要运行的插件的方式有两种:

  • 设置 kubebuilder init --plugins=<plugin key>,将初始化一个为键为 <plugin key> 的插件配置的项目。

  • 在生成的 项目配置文件 中的 layout: <插件键>。在运行命令时(除了初始化命令,它会生成此文件),命令会查看该值以选择要运行的插件。

默认情况下,<plugin key> 将为 go.kubebuilder.io/vX,其中 X 是某个整数。

要查看完整的实现示例,请查看 Kubebuilder 的原生 go.kubebuilder.io 插件。

插件命名

插件名称必须是 DNS1123 标签,并且应为完全合格的名称,即它们有一个后缀,如 .example.com。例如,使用 kubebuilder 命令的基础 Go 脚手架名称为 go.kubebuilder.io。合格的名称可以避免插件名称之间的冲突;go.kubebuilder.iogo.example.com 都可以生成 Go 代码,并且可以由用户指定。

插件版本管理

插件的 Version() 方法返回一个 plugin.Version 对象,包含一个整数值和可选的阶段字符串,它可以是“alpha“或“beta“。该整数表示插件的当前版本。两个不同的整数值表示插件版本之间的不兼容性。阶段字符串表示插件的稳定性:

  • alpha:应该用于那些经常更改并可能在使用之间出现故障的插件。
  • beta:应适用于仅在小幅度上进行更改的插件,例如错误修复。

标准文本

Kubebuilder 内部插件使用模板来生成代码文件。Kubebuilder 通过模板化来搭建插件的文件。例如,在创建新项目时,go/v4 插件会使用其实现中定义的模板来生成 go.mod 文件。

您可以通过定义自己的模板并使用 Kubebuilder 的机械库 来生成文件,从而扩展您自定义插件的功能。该库允许您:

  • Define file I/O behaviors.
  • 标记添加到搭建的文件中。
  • 为您的脚手架指定模板。

示例:样板文本

例如,go/v4 通过定义一个实现了 machinery 接口 的对象来搭建 go.mod 文件。原始模板被设置为 Template.SetTemplateDefaults 方法中的 TemplateBody 字段:

/*******************************************************************************
版权所有 2022 Kubernetes 作者。

根据 Apache 许可证,版本 2.0("许可证")授权;
除非遵守许可证,否则您不得使用此文件。
您可以在以下网址获取许可证的副本:

    http://www.apache.org/licenses/LICENSE-2.0

除非适用法律要求或书面达成协议,否则根据许可证分发的软件是以"原样"基础分发的,
不提供任何种类的保证或条件,明示或暗示。
有关许可证的具体语言,请参见许可证以了解管理权限和
根据许可证的限制。
******************************************************************************/

package templates

import (
	"sigs.k8s.io/kubebuilder/v4/pkg/machinery"
)

var _ machinery.Template = &GoMod{}

// GoMod生成一个定义项目依赖的文件type GoMod struct {
	machinery.TemplateMixin
	machinery.RepositoryMixin

	ControllerRuntimeVersion string
}

// SetTemplateDefaults实现了machinery.Templatefunc (f *GoMod) SetTemplateDefaults() error {
	if f.Path == "" {
		f.Path = "go.mod"
	}

	f.TemplateBody = goModTemplate

	f.IfExistsAction = machinery.OverwriteFile

	return nil
}

const goModTemplate = `模块 {{ .Repo }}

go 1.23.0

godebug 默认=go1.23

依赖 (
	sigs.k8s.io/controller-runtime {{ .ControllerRuntimeVersion }}
)
`

实现该机械接口的对象随后将传递给脚手架的执行:

// Scaffold 实现了 cmdutil.Scaffolder
func (s *initScaffolder) Scaffold() error {
	log.Println("Writing scaffold for you to edit...")

	// 初始化将把样板文件写入磁盘的机器架构。
	// 样板文件需要作为一个单独步骤进行搭建,因为它将被
	// 其他文件使用,包括在此命令调用中搭建的文件。	scaffold := machinery.NewScaffold(s.fs,
		machinery.WithConfig(s.config),
	)

	...

	return scaffold.Execute(
		...
		&templates.GoMod{
			ControllerRuntimeVersion: ControllerRuntimeVersion,
		},
		...
	)
}

示例:在插件中覆盖文件

让我们假设,当调用一个子命令时,你想要覆盖一个现有的文件。

例如,要修改 Makefile 并添加自定义构建步骤,在模板的定义中可以使用以下选项:

f.IfExistsAction = machinery.OverwriteFile

通过使用这些选项,您的插件可以控制由 Kubebuilder 的默认脚手架生成的某些文件。

定制现有支架

Kubebuilder 提供了实用函数,帮助您修改默认的脚手架。通过使用 插件工具,您可以向 Kubebuilder 生成的文件插入、替换或附加内容,从而完全控制脚手架的过程。

这些工具使您能够:

  • 插入内容:在文件的特定位置添加内容。
  • 替换内容:搜索并替换文件的特定部分。
  • 追加内容:在文件末尾添加内容,而不删除或更改现有内容。

Example

如果您需要在生成的文件中插入自定义内容,可以使用插件工具提供的 InsertCode 函数:

pluginutil.InsertCode(filename, target, code)

这种方法使您能够在构建自定义插件时扩展和修改生成的脚手架。

有关更多细节,请参考 Kubebuilder 插件工具

捆绑插件

插件可以被打包以组合成更复杂的脚手架。插件包是多个插件的组合,这些插件按照预定义的顺序执行。例如:

myPluginBundle, _ := plugin.NewBundle(
    plugin.WithName("myplugin.example.com"),
    plugin.WithVersion(plugin.Version{Number: 1}),
    plugin.WithPlugins(pluginA.Plugin{}, pluginB.Plugin{}, pluginC.Plugin{}),
)

该捆绑包将按照指定的顺序为每个插件执行 init 子命令:

  1. pluginA
  2. pluginB
  3. 插件C

以下命令将运行捆绑的插件:

kubebuilder init --plugins=myplugin.example.com/v1

命令行系统

插件是通过 CLI 对象运行的,该对象将插件类型映射到子命令,并调用该插件的方法。例如,编写一个将 Init 插件注入到 CLI 的程序,然后调用 CLI.Run() 将调用插件的 SubcommandMetadataUpdatesMetadataRun 方法,使用用户在 kubebuilder init 中传递给程序的信息。以下是一个示例:

package cli

import (
	log "github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"sigs.k8s.io/kubebuilder/v4/pkg/cli"
	cfgv3 "sigs.k8s.io/kubebuilder/v4/pkg/config/v3"
	"sigs.k8s.io/kubebuilder/v4/pkg/plugin"
	kustomizecommonv2 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/common/kustomize/v2"
	"sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang"
	deployimage "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1"
    golangv4 "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/v4"

)

var (
	// 以下是您自己的二进制文件中可能包含的命令示例
	//	commands = []*cobra.Command{
		myExampleCommand.NewCmd(),
	}
	alphaCommands = []*cobra.Command{
		myExampleAlphaCommand.NewCmd(),
	}
)

// GetPluginsCLI 返回配置在您的 CLI 二进制文件中使用的基于插件的 CLI。func GetPluginsCLI() (*cli.CLI) {
	// 打包插件,用于构建由 Kubebuilder go/v4 创建的 Golang 项目脚手架。	gov3Bundle, _ := plugin.NewBundleWithOptions(plugin.WithName(golang.DefaultNameQualifier),
		plugin.WithVersion(plugin.Version{Number: 3}),
		plugin.WithPlugins(kustomizecommonv2.Plugin{}, golangv4.Plugin{}),
	)


	c, err := cli.New(
		// 添加你的 CLI 二进制文件的名称
		cli.WithCommandName("example-cli"),

		// 添加你的 CLI 二进制文件的版本。		cli.WithVersion(versionString()),

		// 注册可以通过你的 CLI 工具使用的插件选项,以进行脚手架。请注意,我们在这里使用的示例是由 Kubebuilder 实现和提供的插件。		cli.WithPlugins(
			gov3Bundle,
			&deployimage.Plugin{},
		),

		// 定义您的二进制文件将使用的默认插件。这意味着如果没有提供信息,例如当用户运行 `kubebuilder init` 时,将使用这个插件。		cli.WithDefaultPlugins(cfgv3.Version, gov3Bundle),

		// 定义默认的项目配置版本,CLI在未通过--project-version标志提供时将使用该版本。		cli.WithDefaultProjectVersion(cfgv3.Version),

		// 将您自己的命令添加到命令行接口 (CLI)		cli.WithExtraCommands(commands...),

		// 将您自己的 alpha 命令添加到 CLI 中
		cli.WithExtraAlphaCommands(alphaCommands...),

		// 为你的命令行界面添加补全选项
		cli.WithCompletion(),
	)
	if err != nil {
		log.Fatal(err)
	}

	return c
}

// versionString 返回 CLI 版本func versionString() string {
	// 返回您的二进制项目版本}

该程序可以通过以下方式构建和运行:

默认行为:

# 使用默认的 Init 插件"go.example.com/v1"初始化项目。
# 此密钥会自动写入 PROJECT 配置文件中。
$ my-bin-builder init
# 使用 "go.example.com/v1" 的 CreateAPI 和 CreateWebhook 插件方法创建 API 和 webhook。此密钥是从配置文件中读取的。$ my-bin-builder create api [flags]
$ my-bin-builder create webhook [flags]

使用 --plugins 选择一个插件:

# 使用 "ansible.example.com/v1" 初始化插件初始化项目。
# 和上面一样,这个密钥会写入配置文件中。
$ my-bin-builder init --plugins ansible
# 使用 "ansible.example.com/v1" 的 CreateAPI 和 CreateWebhook 插件方法创建 API 和 Webhook。此密钥是从配置文件中读取的。$ my-bin-builder create api [flags]
$ my-bin-builder create webhook [flags]

输入应在项目文件中跟踪。

CLI 负责管理 项目文件配置,该配置表示由 CLI 工具搭建的项目的配置。

在扩展 Kubebuilder 时,建议确保您的工具或 外部插件 正确使用 PROJECT 文件 来跟踪相关信息。这确保了其他外部工具和插件可以与项目正确集成。它还允许工具特性帮助用户重新构建他们的项目,例如 Kubebuilder 提供的 项目升级助手,确保 PROJECT 文件中跟踪的信息可以用于多种目的。

例如,插件可以检查它们是否支持项目设置,并根据跟踪的输入重新执行命令。

Example

通过运行以下命令使用 Deploy Image 插件来搭建 API 及其控制器:

kubebyilder create api --group example.com --version v1alpha1 --kind Memcached --image=memcached:memcached:1.6.26-alpine3.19 --image-container-command="memcached,--memory-limit=64,-o,modern,-v" --image-container-port="11211" --run-as-user="1001" --plugins="deploy-image/v1-alpha" --make=false

以下条目将被添加到项目文件中:

...
plugins:
  部署图像.go.kubebuilder.io/v1-alpha:
    resources:
    - domain: testproject.org
      group: example.com
      kind: Memcached
      选项:
        容器命令: memcached,--memory-limit=64,-o,modern,-v
        容器端口: "11211"
        image: memcached:memcached:1.6.26-alpine3.19
        以用户身份运行: "1001"
      version: v1alpha1
    - domain: testproject.org
      group: example.com
      kind: Busybox
      选项:
        image: busybox:1.36.1
      version: v1alpha1
...

通过检查 PROJECT 文件,可以了解插件是如何使用的以及提供了哪些输入。这不仅可以基于跟踪的数据重新执行命令,还能够创建可以依赖这些信息的功能或插件。