bk-plugin-framework-go 是一个轻量化系统插件开发框架,开发者只需要使用该框架进行插件开发,并将其部署到蓝鲸 PaaS 平台上,即可完成系统插件的开发和接入。
接入系统通过调用 bk-plugin-framework-go 暴露出来的标准接口,完成系统插件功能的实现。
传送门: BK-PLUGIN-CLIENT-GO
一个插件由以下元素构成
- Meta:插件元数据
- Inputs:插件调用输入模型
- ContextInputs:插件调用上下文输入模型
- Outputs:插件输出模型
- execute method:插件调用逻辑
├── Procfile
├── app_desc.yml
├── bin
│ ├── pre_compile
│ └── post_compile
├── bk_plugin
│ ├── v100
│ └── plugin.go
├── app.json
├── go.mod
├── main.go
bk_plugin:存放每个版本的插件执行代码,所有插件定义必须位于该目录下,目录下文件名不做限制,框架会自动从该目录下发现插件。
go.mod: 存放当前插件运行需要的依赖版本。
- Procfile:插件运行时进程定义文件
- app_desc.yml:插件描述文件
- bin/maange.py:运行时命令入口
- bin/post_compile:插件部署后置操作
一个插件在一次执行生命周期中可能会经过下图所示的状态转换,每种状态说明如下:
- EMPTY:初始状态,每次插件调用都会从这个状态开始
- SUCCESS:执行成功状态,插件在
execute
方法中如果没有抛出任何异常,没有self.wait_poll
和self.wait_callback
的调用,就会进入成功状态,SUCCESS 是一次调用的结束状态 - FAIL:执行失败状态,一旦插件在
execute
方法中抛出任何已知或不可预知的异常,都会进入失败状态,FAIL 也是一次调用的结束状态 - POLL:轮询状态,插件一旦在
execute
中调用了self.wait_poll
方法,并且后续没有抛出异常,就会进入轮询状态,处于 POLL 状态的调用会在一定时间后被运行时拉起,并再次执行execute
方法。 - CALLBACK:回调状态,插件一旦在
execute
中调用了self.wait_callback
方法,并且后续没有抛出异常,就会进入回调状态,处于 CALLBACK 状态的插件会等待来自外部的回调,收到回调后,该次调用会被运行时拉起,并再次执行execute
方法。
通过框架提供的方法,你可以在插件逻辑中进行任意合法的状态流转,以适配不同的业务场景
一个完整的go的蓝鲸插件的结构应该如下:
- 插件的表单定义部分,这部分指的是
InputsForm
,这部分遵循jsonSchema协议,详情请查看: - 插件的输入部分,这部分指的是
Inputs
- 插件的上下文部分,这部分对应的是
ContextInputs
- 插件输出定义部分,这部分对应的是
Outputs
- Version(), 插件的对应的获取版本的方法
- Desc(),插件对象的获取描述的方法
- Execute() 插件的执行方法,执行方法接收一个 Context 上下文对象。
package v100
import (
"github.com/TencentBlueKing/bk-plugin-framework-go/kit"
)
var InputsForm kit.Form = kit.Form{
"template_id": kit.F{
"ui:component": kit.F{"name": "bk-input", "props": kit.F{"type": "textarea"}},
"ui:reactions": []kit.F{},
},
}
type Inputs struct {
TemplateID int `json:"template_id" jsonschema:"title=模板 ID"`
}
type ContextInputs struct {
BKBizID string `json:"bk_biz_id" jsonschema:"title=蓝鲸 CMDB ID"`
}
type Outputs struct {
TaskID int `json:"task_id" jsonschema:"title=任务 ID"`
TaskURL string `json:"task_url" jsonschema:"title=任务 URL"`
}
type Store struct {
TaskID int
TaskURL string
}
type Plugin struct{}
func (p *Plugin) Version() string {
return "1.0.0"
}
func (p *Plugin) Desc() string {
return "执行标准运维作业"
}
func (p *Plugin) Execute(c *kit.Context) error {
return nil
}
在main.go 文件中,需要注册我们定义好的插件到hub中,可以同时注册多个不同版本到插件
package main
import (
v100 "bk-plugin-go/versions/v100"
"github.com/TencentBlueKing/beego-runtime/runner"
"github.com/TencentBlueKing/bk-plugin-framework-go/hub"
)
func main() {
hub.MustInstall(&v100.Plugin{}, v100.Inputs{}, v100.ContextInputs{}, v100.Outputs{}, v100.InputsForm)
runner.Run()
}
插件上下文 Context对象中的提供了一组方法, 可以读取插件所需要的状态,输入,上下文等信息。具体可以看如下示例。
func (p *Plugin) Execute(c *kit.Context) error {
// 读取插件状态对象
state := c.State()
switch state {
// 如果插件状态为 constants.StateEmpty 则说明插件是第一次执行,此时执行的是execute逻辑
case constants.StateEmpty:
var inputs Inputs
// c.ReadInputs(&inputs) 将插件的input 反序列化到对象中,这里的Inputs是我们上文中定义的Inputs
if err := c.ReadInputs(&inputs); err != nil {
return err
}
// 定义一个插件上下文对象
var contextInputs ContextInputs
// c.ReadContextInputs() 读取插件上下文到上文中定义的 ContextInputs 对象中
if err := c.ReadContextInputs(&contextInputs); err != nil {
return err
}
log.Printf("create sops task with %v and %v\n", inputs, contextInputs)
// request with inputs
taskID := 123
taskURL := "task_url"
outputs := Outputs{
TaskID: taskID,
TaskURL: taskURL,
}
// 将插件的输出 Outputs 写入到 Outputs 中,调用c.WriteOutputs()
if err := c.WriteOutputs(&outputs); err != nil {
return err
}
// c.Write(obj) 将某个对象写入插件执行上下文中,当插件处于轮询逻辑时,将会读到
store := Store{TaskID: taskID, TaskURL: taskURL}
if err := c.Write(&store); err != nil {
return nil
}
// 设置下一次轮询为5s之后
c.WaitPoll(5)
return nil
// constants.StatePoll 表示此时插件处于轮询态
case constants.StatePoll:
var store Store
// 读取在上文中写入的 Store 对象
if err := c.Read(&store); err != nil {
return nil
}
taskState := "RUNNING"
if c.InvokeCount() >= 5 {
taskState = "FINISHED"
}
switch taskState {
case "RUNNING":
c.WaitPoll(5)
case "FAILED":
return fmt.Errorf("task %v execute fail", store.TaskID)
}
return nil
}
return fmt.Errorf("invalid state %v", state)
}
如果你的插件发生了以下任一项或多项破坏性的改动,为了不影响插件现有版本的使用,请开发一个新版本插件:
- 插件的输入模型中
增加
了必填的输入参数 - 插件的输入模型中
删除
了必填的输入参数 - 插件的上下文输入模型中
增加
了必填的输入参数 - 插件的上下文输入模型中
删除
了必填的输入参数 - 插件的输出模型中
删除
了某个输出参数 - 插件的输出模型中某个输出参数的
类型
发生了变化 - 插件的表单
增加
了必填的参数 - 插件的表单
删除
了必填的参数 - 插件的表单
数据结构
发生了变化 - 插件的功能发生了翻天覆地的变化
插件的 execute
方法定义了插件的执行逻辑,该方法必须接受两个输入参数:inputs: Inputs
与 context: Context
。
在插件执行过程中,如果遇到了需要让本次调用进入失败状态的情况(例如,外部接口调用失败),可以通过抛出 返回一个 err对象
来让插件进入失败状态
func (p *Plugin) Execute(c *kit.Context) error {
return fmt.Errorf("error")
}
在某些场景下,依次调用执行的任务可能会耗费很长时间,这时候如果一直在 execute 中使用 while 来等待是不太合适的,此时我们可以调用 context.WaitPoll(interval) 方法来让本次调用进入等待调度状态,当 wait_poll 调用成功且 execute 正常返回后,execute 方法会在 interval 秒后被再次拉起执行。 可以通过获取context对象的State()方法来获取当前的执行状态。
状态有以下几种情况:
const (
StateEmpty State = 1
StatePoll State = 2
StateCallback State = 3
StateSuccess State = 4
StateFail State = 5
)
func (p *Plugin) Execute(c *kit.Context) error {
// 读取插件状态对象
state := c.State()
switch state {
// 如果插件状态为 constants.StateEmpty 则说明插件是第一次执行,此时执行的是execute逻辑
case constants.StateEmpty:
c.WaitPoll(5)
// 设置下一次轮询为5s之后
return nil
// constants.StatePoll 表示此时插件处于轮询态
case constants.StatePoll:
return nil
}
return fmt.Errorf("invalid state %v", state)
若 execute 中如果没有抛出任何异常,没有 context.wait_poll 和 context.wait_callback 的调用,就会进入成功状态
插件的input需要定义两份数据,一份是插件input对应的结构体,一份是插件input对应的jsonschema定义。具体使用参数参考jsonschema规范。 以下实例定义了两个输入,其中task_name 是一个下拉框,下拉框的数据源是dataSource对象
var dataSource = map[string]string{"label": "测试", "value": "测试"}
var InputsForm kit.Form = kit.Form{
"template_id": kit.F{
"ui:component": kit.F{"name": "bk-input", "props": kit.F{"type": "textarea"}},
"ui:reactions": []kit.F{},
},
"task_name": kit.F{
"ui:component": kit.F{"name": "select", "props": kit.F{"type": "select", "datasource": []map[string]string{dataSource}}},
"ui:reactions": []kit.F{},
},
}
type Inputs struct {
TemplateID int `json:"template_id" jsonschema:"title=模板 ID"`
TaskName string `json:"task_name" jsonschema:"title=任务名"`
}
环境准备: 请确保本地已经安装了go 16+ 版本的sdk。同时安装了以下组件:
- redis: 要求redis版本为4.0+。
后续的所有命令操作,请确保当前会话中存在以下环境变量:
export REDIS_HOST="127.0.0.1" # 默认为127.0.0.1
export REDIS_PORT="6079" # 默认为6079
export REDIS_PASSWORD="" # 默认为空
启动调试进程:
- 执行bee run 命令,生成对应的二进制命令,如 bk-plugin-go
- 启动web服务
// web
bk-plugin-go server
// worker
bk-plugin-go worker
在开发标准运维系统插件时,可以在上下文输入中定义这些字段,标准运维会根据当前流程任务执行的上下文传入插件中:
- project_id(int):当前任务所属标准运维项目ID
- project_name(str):当前任务所属标准运维项目名
- bk_biz_id(int):当前任务所属 CMDB 业务 ID
- bk_biz_name(str):当前任务所属 CMDB 业务名
- operator(str):当前任务操作者(点击开始按钮的人)
- executor(str):当前任务执行人(调用第三方系统 API 使用的身份)
- task_id(int):当前任务 ID
- task_name(str):当前任务名
- 蓝鲸论坛
- 蓝鲸 DevOps 在线视频教程
- 联系我们,技术交流QQ群:
- BK-CI:蓝鲸持续集成平台是一个开源的持续集成和持续交付系统,可以轻松将你的研发流程呈现到你面前。
- BK-BCS:蓝鲸容器管理平台是以容器技术为基础,为微服务业务提供编排管理的基础服务平台。
- BK-PaaS:蓝鲸PaaS平台是一个开放式的开发平台,让开发者可以方便快捷地创建、开发、部署和管理SaaS应用。
- BK-SOPS:标准运维(SOPS)是通过可视化的图形界面进行任务流程编排和执行的系统,是蓝鲸体系中一款轻量级的调度编排类SaaS产品。
- BK-CMDB:蓝鲸配置平台是一个面向资产及应用的企业级配置管理平台。
如果你有好的意见或建议,欢迎给我们提 Issues 或 Pull Requests,为蓝鲸开源社区贡献力量。
- 本项目使用 Poetry 进行开发、构建及发布,本地开发环境搭建请参考 Poetry 官方文档
- PR 需要通过 CI 中的所有代码风格检查,单元测试及集成测试才可被接受合并
- 新增加的模块请确保完备的单元测试覆盖
基于 MIT 协议, 详细请参考LICENSE