写在前面
开发插件前,先进入插件工作台初始化一个插件,确定插件在平台中的唯一标识
工作台
可以在这里进行新增/发布/下架等管理插件的操作
功能区介绍
- 切换资源类型
- 新增插件
- 单个插件的管理入口
- 升级、下架、删除插件快捷入口
- 指引文档和插件 UI 调试工具入口
新增插件
- 标识
- 插件在平台中的唯一标识,建议取和插件功能相关的可读性好的英文标识
- 调试项目
- 插件发布过程中,可以在调试项目下将插件添加到流水线执行,对插件进行测试,保证插件功能满足预期。
- 建议新增专用的插件调试项目,避免测试过程中影响到业务。
- 开发语言
- 支持四种语言开发插件:
- Java(推荐)
- Python
- Golang
- Nodejs
开发插件
初始化好插件之后,可以开始开发插件
- 根据开发语言参考对应的开发指引
- Java 插件开发指引
- Python 插件开发指引
- Golang 插件开发指引
- Nodejs 插件开发指引
插件私有配置
插件级别的敏感信息,如 token、用户名密码、IP、域名等,不建议直接提交到代码库,通过工作台私有配置界面管理
Golang 插件开发
插件开发框架说明
插件最终打包成一个命令行可执行的命令即可,对开发框架无硬性要求 下边以 demo 插件为例示范
示例插件代码工程的整体结构如下
|- <你的插件标识>|- cmd|- application|- main.go|- hello|- hello.go
如何开发插件:
参考 plugin-demo-golang
- 创建插件代码工程
- 插件代码建议企业下统一管理。 通用的开源插件可以联系蓝鲸官方放到 TencentBlueKing 下,供更多用户使用
- 实现插件功能
- 规范:
- 插件开发规范
- 插件配置规范
- 插件前端不仅可以通过 task.json 进行标准化配置,也可以自定义开发:
- 自定义插件 UI 交互指引
- 插件输出规范
- 插件错误码规范
- 插件发布包规范
Demo示例研读
将 plugin-demo-golang clone到本地。
来看下项目结构:
.
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── i18n
│ ├── message_en_US.properties
│ └── message_zh_CN.properties
├── main.go
├── task.json
└── translation└── translation.go3 directories, 11 files
咦!和上面说的插件代码工程的整体架构不一样:
|- <你的插件标识>|- cmd|- application|- main.go|- hello|- hello.go
不过,这不重要,只要插件最终能够打包成一个命令行可执行的命令即可。
这里i18n是实现中英文国际化使用的,
├── i18n
│ ├── message_en_US.properties
│ └── message_zh_CN.properties
看下内容对比:
message_en_US.properties:
input.desc.label=desc
100001=input param [{0}] invitated
message_zh_CN.properties
input.desc.label=描述
100001=输入参数[{0}]非法
那我们就可以猜测,这里是实现输入参数[{0}]非法这句话的中英文,其中[{0}]会使用具体的参数填充。
然后i18ngenerator则是根据properties文件的配置,生成translation代码,如translation.go:
// Code generated by "i18ngenerator"; DO NOT EDIT.package translation// Translations
var Translations map[string][][]string = make(map[string][][]string)func init() {Translations["en-US"] = [][]string{{"100001","input param [{0}] invitated",},{"input.desc.label","desc",},}Translations["zh-CN"] = [][]string{{"100001","输入参数[{0}]非法",},{"input.desc.label","描述",},}
}
该文件是由i18ngenerator自动生成的,不要自己改。
main函数内,实现了一个小的输出功能,简单看下源码,然后去进行测试:
package mainimport ("fmt""io/ioutil""os""runtime""time""github.com/ci-plugins/golang-plugin-sdk/api""github.com/ci-plugins/golang-plugin-sdk/log""github.com/ci-plugins/plugin-demo-golang/translation"
)//go:generate i18ngenerator i18n ./translation/translation.gotype greetingParam struct {UserName string `json:"userName"`Greeting string `json:"greeting"`
}func (a *greetingParam) String() string {return fmt.Sprintf("userName: %v, greeting: %v", a.UserName, a.Greeting)
}func main() {runtime.GOMAXPROCS(4)log.Info("atom-demo-glang starts")defer func() {if err := recover(); err != nil {log.Error("panic: ", err)api.FinishBuild(api.StatusError, "panic occurs")}}()api.InitI18n(translation.Translations, api.GetRuntimeLanguage())msg, err := api.Localize("input.desc.label")if err != nil {log.Error(err)}log.Info(msg)helloBuild()
}func helloBuild() {// 获取单个输入参数userName := api.GetInputParam("userName")log.Info("userName: ", userName)// 打屏log.Info("\nBuildInfo:")log.Info("Project Name: ", api.GetProjectDisplayName())log.Info("Pipeline Id: ", api.GetPipelineId())log.Info("Pipeline Name: ", api.GetPipelineName())log.Info("Pipeline Version: ", api.GetPipelineVersion())log.Info("Build Id: ", api.GetPipelineBuildId())log.Info("Build Num: ", api.GetPipelineBuildNumber())log.Info("Start Type: ", api.GetPipelineStartType())log.Info("Start UserId: ", api.GetPipelineStartUserId())log.Info("Start UserName: ", api.GetPipelineStartUserName())log.Info("Start Time: ", api.GetPipelineStartTimeMills())log.Info("Workspace: ", api.GetWorkspace())// 输入参数解析到对象paramData := new(greetingParam)api.LoadInputParam(paramData)log.Info(fmt.Sprintf("\n%v,%v\n", paramData.Greeting, paramData.UserName))// 业务逻辑log.Info("start build")build()time.Sleep(2 * time.Second)// 输出// 字符串输出strData := api.NewStringData("test")api.AddOutputData("strData_01", strData)// 文件归档输出artifactData := api.NewArtifactData()artifactData.AddArtifact("result.dat")api.AddOutputData("artifactData_02", artifactData)// 报告输出reportData := api.NewReportData("label_01", api.GetWorkspace()+"/report", "report.htm")api.AddOutputData("report_01", reportData)api.WriteOutput()log.Info("build done")
}func build() {log.Info("write result.dat")ioutil.WriteFile(api.GetWorkspace()+"/result.dat", []byte("content"), 0644)log.Info("write report.htm")os.Mkdir(api.GetWorkspace()+"/report", os.ModePerm)ioutil.WriteFile(api.GetWorkspace()+"/report/report.htm", []byte("<html><head><title>Report</title></head><body><H1>This is a Report</H1></body></html>"), 0644)
}
在根目录下已经给我们预设了一个task.json文件,后面可以简单修改下这个文件来实现测试:
{"atomCode": "goDemo","execution": {"language": "golang","packagePath": "goDemo","demands": ["chmod +x goDemo"],"target": "./goDemo"},"input": {"greeting": {"label": "欢迎词","default": "Glad to see you","placeholder": "欢迎词","type": "vuex-input","desc": "欢迎词","required": true,"disabled": false,"hidden": false,"isSensitive": false},"userName": {"label": "姓名","default": "Mr. Huang","placeholder": "姓名","type": "vuex-input","desc": "姓名","required": true,"disabled": false,"hidden": false,"isSensitive": false}},"output": {"strData_01": {"description": "测试","type": "string","isSensitive": false}}
}
如何打包发布
1、进入插件代码工程目录下
2、打包
2.1、如果按照正常的demo的目录结构是需要进入cmd/application内执行build命令,因为main在此
cd cmd/application
GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o bin/${executable}
2.2、这次用的demo则直接在根目录下执行build命令,因为main在根目录下
GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o bin/${executable}
这里go build -o bin/ e x e c u t a b l e 会在 b i n 目录下,生成可执行文件,文件名是 {executable}会在bin目录下,生成可执行文件,文件名是 executable会在bin目录下,生成可执行文件,文件名是{executable},即项目名。
也可以自定义一个名字,如GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o bin/kingtest
- 在任意位置新建文件夹,命名示例:release_pkg = <你的插件标识>_release
- 将步骤 2 生产的执行包拷贝到 <release_pkg> 下
- 添加 task.json 文件到 <release_pkg> 下 task.json 见示例,按照插件功能配置。
mkdir kingtest_release
cp bin/kingtest kingtest_release/kingtest
touch kingtest_release/task.json
插件配置规范: 插件配置规范
task.json 示例:
{"atomCode": "king-test", # atomCode 要与工作台录入的一致"execution": {"language": "golang","packagePath": "kingtest", # 发布包中插件安装包的相对路径"demands": ["echo start run chmod +x kingtest", # 插件启动前需要执行的安装命令,顺序执行"chmod +x kingtest", # 插件启动前需要执行的安装命令,顺序执行"echo stop run chmod +x kingtest", # 插件启动前需要执行的安装命令,顺序执行],"target": "./kingtest"},"input": {"greeting": {"label": "欢迎词","default": "Glad to see you","placeholder": "欢迎词","type": "vuex-input","desc": "欢迎词","required": true,"disabled": false,"hidden": false,"isSensitive": false},"userName": {"label": "姓名","default": "Mr. Huang","placeholder": "姓名","type": "vuex-input","desc": "姓名","required": true,"disabled": false,"hidden": false,"isSensitive": false}},"output": {"strData_01": {"description": "测试","type": "string","isSensitive": false}}
}
- 在 <release_pkg> 目录下,把所有文件打成 zip 包即可
cd kingtest_release && zip kingtest_release.zip kingtest task.json
zip包结构示例:
|- kingtest_release.zip # 发布包|- kingtest # 插件执行包|- task.json # 插件配置文件
打包完成后,在插件工作台提单发布,即可测试或发布插件
上传一个流水线插件
开发好插件之后,通过研发商店工作台,将插件发布到研发商店,提供给用户添加到流水线中使用。
入口
在工作台列表,点击如下入口发起发布流程:
首次发布时,入口名为上架
后续更新版本时,入口名为升级
或者在插件发布管理->版本管理界面发起发布流程:
填写插件相关信息/上传插件发布包
上架/升级插件时,可以修改插件的基本信息,如下所示:
- 适用 Job 类型:
- 和流水线 Job 类型对应,请按照插件实际适用情况选择
- 若选错,需新增版本修改
- 发布包:
- task.json 中的 atomCode 需和 新增插件时填写的标识一致,否则上传会失败
测试/发布插件
填写好信息,提交后,进入发布流程,可以测试->重新传包->测试,直至插件满足预期后,手动继续流程将插件发布到研发商店
- 测试:点击后跳转到插件调试项目的流水线服务下,可以将当前插件添加到流水线,验证 UI、功能是否满足预期
- 重新传包:当测试发现问题,修复后,重新上传发布包,再次进行测试
- 继续:测试 OK,满足预期后,确认提交发布
- 取消发布:发布过程中,随时可以终止发布
遇见的几个错误
无权限执行
在测试中遇见一个问题:无权限执行
在execution->demands
增加一个命令chmod +x kingtest
即可解决
发布进度里重新传包持续报错task.json格式错误
还有个问题,在发布进度里重新传包时,一直报错task.json格式错误,但实际格式是对的!
触发的原因暂不知道,但是确实是一个隐藏的bug。
直接点击继续,然后走升级插件的方式可以正常使用。
cannot execute binary file: Exec format error
这里的问题是我造成的,最初我在mac环境下编译的可执行文件,命令是:
# mac下执行
go build -o bin/kingtest
但是插件里选择的编译环境是linux。
解决方式:让插件选择的编译环境和可执行文件的平台统一。
我这里选择重新编译下可执行文件,采用在mac平台交叉编译linux平台可执行文件的方式。
GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o bin/kingtest
也可以新建一个插件,编译环境选择mac。
运行结果
流水线结果:
输出结果:
[Plugin info]
=====================================================================
Task : king-test
Description : bk插件测试
Version : 1.0.3
Author : huari
Help : More Information
=====================================================================
-----
[Input]
input(normal): (欢迎词)greeting=Glad to see you
input(normal): (姓名)userName=Mr. Huang
-----
[Install plugin]
-----
start run chmod +x kingtest
stop run chmod +x kingtest
atom-demo-glang starts
描述
userName: Mr. HuangBuildInfo:
Project Name: GOPS
Pipeline Id: p-8967ed52b08847c8a5b0140937db0975
Pipeline Name: king-test
Pipeline Version: 11
Build Id: b-78947b5c39f34b32bbafb803042d1e22
Build Num: 15
Start Type: MANUAL
Start UserId: huari
Start UserName: huari
Start Time: 1733298107286
Workspace: /data/devops/workspaceGlad to see you,Mr. Huang
start build
write result.dat
write report.htm
build done
[Output]
1 file match: /data/devops/workspace/result.dat
prepare to upload 7 B
1/1 file(s) finished
output(except): artifactData_02=result.dat
入口文件检测完成
上传自定义产出物成功,共产生了1个文件
output(except): report_01=report.htm
output(normal): strData_01=test
-----