Kong 开源贡献指南:PR 规范、Commit 约定、测试体系与 Lua 代码风格
本文基于 Kong 仓库根目录的 CONTRIBUTING.md 展开,系统梳理向 Kong(The API and AI Gateway)提交代码的完整流程:从问题上报渠道、Pull Request 提交前检查清单、Git 分支命名与 Commit Message 格式,到静态 Lint、busted 测试体系、changelog 编写要求、面向 LuaJIT 的性能编码实践,以及一套完整的 Lua 代码风格规范。读完后,你可以按官方约定独立提交一个符合仓库要求的补丁,并理解每个规范背后在仓库中的实际落点(如 Makefile、.luacheckrc、spec/ 测试目录结构、changelog/ 目录)。
一、寻求帮助与上报问题的渠道
在动手写补丁之前,CONTRIBUTING.md 首先区分了两类支持渠道:
1. 企业版(Enterprise Edition)支持
Kong Enterprise 客户应通过企业支持渠道提单,而不是在社区渠道提问;P1 级别的故障应拨打 24/7 企业支持热线。有意成为 Enterprise 客户则联系官方销售。
2. 社区版(Community Edition)支持
对于社区版的使用问题,官方建议:
- 使用 GitHub Discussions 提问;
- 加入社区 Slack 进行实时交流;
- 不要在 GitHub Issues 中提出一般性使用问题或求助——Issues 仅保留给真正的 bug 报告;
- 公开论坛 Kong Nation 适合提问、答疑和跟踪最新公告。
3. 如何报告一个 Bug
在 GitHub 仓库提交 issue 时,必须遵循 issue 模板,并包含以下四项信息:
- 问题摘要(A summary of the issue);
- 帮助复现问题的步骤列表;
- 遇到问题时的 Kong 版本;
- 你的 Kong 配置,或其中与问题相关的部分。
报告 bug 的同时,你也被欢迎直接提交修复补丁。功能请求(feature request)同样通过 issue 提交,且应尽可能详细。
二、贡献形式:不写代码也可以参与
除了代码增强和 bug 修复,CONTRIBUTING.md 列出了多种贡献方式:
- 报告 bug;
- 在支持渠道帮助社区成员;
- 修复代码中的错别字;
- 修复官方文档中的错别字、补充示例或澄清说明;
- 对提出的特性和设计提供反馈;
- Review Pull Requests。
提议新插件(重要边界)
官方明确说明:一般不会接受把新插件合并进本仓库。当前仓库中随 Kong 分发的插件集合是“基础插件集”,面向所有 Kong 安装环境。专用功能应以独立仓库中的插件形式存在。如果你需要写插件,官方建议:
- 先阅读 Plugin Development Guide;
- 将插件托管在公开仓库,并通过 LuaRocks 分发;
- 为插件增加曝光度:添加到 Kong Hub、在社区论坛发布公告。
这个边界也解释了仓库中 kong/plugins/ 目录的构成——从源码结构看,该目录下是随核心分发的一等公民插件(如 key-auth、jwt、rate-limiting、ai-proxy 等),而外部插件机制则由 kong/runloop/plugin_servers/ 与 spec/02-integration/10-external-plugins/ 下的测试用例支撑。
三、提交补丁(Submitting a Patch)
小改动比大改动更容易被快速合并。如果你计划开发较大的特性,官方建议先在 GitHub Discussions 中沟通。提交 PR 前,必须完成以下检查清单:
- 提交历史干净:变更是原子的,且遵循 commit message 格式;
- Rebase 到基线分支:
git rebase保证提交历史干净且线性; - 静态 Lint 通过:运行
make lint或luacheck .; - 测试通过:运行
make test、make test-all或针对你的变更选择合适的测试套件; - 不要在 PR 中更新
CHANGELOG.md:该文件在发布流程中自动重新生成和维护。
如果 Reviewer 要求你修改补丁,请务必配合——CONTRIBUTING.md 特别强调:推动你的补丁前进是你的责任。PR 被接受后(修复 bug、增加功能或显著改善可用性),你就成为官方贡献者,可以申请 Contributor Badge,并且作为外部贡献者,你的名字会出现在后续版本的 changelog 中。
Git 分支命名规范
如果你有仓库的推送权限,请遵循以下分支命名约定:
| 前缀 | 用途 |
|---|---|
feat/foo-bar |
新特性 |
fix/foo-bar |
Bug 修复 |
tests/foo-bar |
仅涉及测试套件的变更 |
refactor/foo-bar |
不改变行为的重构 |
style/foo-bar |
风格问题 |
docs/foo-bar |
README.md、CONTRIBUTING.md 等文档更新 |
chore/foo-bar |
不涉及功能源码的变更 |
perf/foo-bar |
性能改进 |
Commit 原子性(Atomicity)
补丁中的提交必须组织为“逻辑工作单元”。一个 PR 可以包含一个或多个 commit,但每个 commit 内部不能混入无关变更。官方给出的典型反例:
你在修一个 bug 时顺手发现了另一个 bug——**不要在同一个 commit 里修两个 bug!**完成第一个 bug 的工作、提交补丁,之后再回来处理第二个 bug。无关的风格修复、重构同理。
判断标准是换位思考:Review 你的人,仅凭阅读你的 commit 历史,能否理解你的变更和理由?能否在某个 commit 里看到无关改动?答案应该是“不能”。遵循后文的 commit message 格式也有助于保持原子性。
四、Commit Message 格式
为了维护健康的 Git 历史,Kong 要求 commit message 满足以下硬性规则:
- 使用现在时(present tense);
- 必须以
type和scope为前缀; - 标题行(header)不超过 50 个字符;
- header 与 body 之间必须有一个空行;
- body 中的每一行不超过 72 个字符。
整体遵循 conventional-commits 格式,模板如下:
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
Type(类型)
接受的 type 有九种:
- feat:新特性;
- fix:Bug 修复;
- hotfix:发布过程中的紧急 bug 修复;
- tests:纯粹与测试套件相关的变更(修测试、加测试、提升可靠性等);
- docs:README.md、CONTRIBUTING.md 或类似文档的变更;
- style:不影响代码含义的变更(空白裁剪、格式化等);
- perf:显著改进性能的代码变更;
- refactor:既不是修 bug 也不是加特性、且大到不能算作
perf的代码变更; - chore:不属于重构的代码清理类维护变更、构建流程更新、依赖升级,或辅助工具与库的更新(LuaRocks、GitHub Actions 等)。
Scope(作用域)
Scope 指变更影响的代码库部分,由作者自行判断,常见的有:
- proxy:影响请求代理的变更;
- router:影响路由器的变更(路由器负责把请求匹配到配置好的 API);
- admin:Admin API 的变更;
- balancer:内部负载均衡器相关;
- core:影响核心大部分、同时触及
proxy、balancer、dns等多处的变更; - dns:内部 DNS 解析相关;
- dao:与 DAO(数据层接口)相关的变更;
- cli:CLI 变更;
- cache:配置实体(数据层实体)缓存相关;
- deps:更新依赖时使用(与
chore前缀搭配); - conf:配置相关变更(新配置项、改进等);
<plugin-name>:插件名,如basic-auth、ldap;*:变更同时影响过多部分时使用(应尽量避免)。
这些 scope 与仓库目录结构可以直接对应:proxy 对应 kong/runloop/handler.lua 等运行循环逻辑,router 对应 kong/router/(含 atc.lua、traditional.lua、expressions.lua 等),admin 对应 kong/api/,dao 对应 kong/db/dao/,dns 对应 kong/dns/,cli 对应 kong/cmd/。
Subject(标题)
Subject 应是对变更的简练描述,且必须:
- 使用现在时、祈使句:写 "fix typo",而不是 "fixed" 或 "fixes";
- 不首字母大写:写 "fix typo",而不是 "Fix typo";
- 不以句号结尾。
Body(正文)
Body 应包含变更的详细说明。如果变更比较重大,应解释其动机和所选实现方案,并给出理由。每行不超过 72 个字符。
Footer(页脚)
Footer 是链接相关材料的理想位置:相关 GitHub issues、PR、被修复的 bug 报告等。
完整示例
CONTRIBUTING.md 给出了两个可直接借鉴的示例:
fix(admin): send HTTP 405 on unsupported method
The appropriate status code when the request method is not supported
on an endpoint is 405. We previously used to send HTTP 404, which
is not appropriate. This updates the Admin API helpers to properly
return 405 on such user errors.
* return 405 when the method is not supported in the Admin API helpers
* add a new test case in the Admin API test suite
Fix #678
另一个:
tests(proxy): add a new test case for URI encoding
When proxying upstream, the URI sent by Kong should be the one
received from the client, even if it was percent-encoded.
This adds a new test case which was missing, to ensure it is
the case.
五、静态 Lint:Luacheck 与仓库配置
Kong 使用 Luacheck 对 Lua 代码做静态检查,两种方式任选:
$ make lint
或:
$ luacheck .
从 Makefile 的源码可以看到 lint 目标的真实构成(第 152–155 行):它不仅运行 luacheck -q .,还会用 grep 检查测试文件中不允许残留的调试标记——spec/ 下的 #only / #o 标签和 t/ 下的 --- ONLY block,一旦发现即让 lint 失败。这防止了开发者把“只跑单个用例”的本地调试标记误提交进仓库。
仓库根目录的 .luacheckrc 定义了检查规则,几个关键点:
- 基础标准是
std = "ngx_lua",即 OpenResty/lua-nginx-module 的 API 集合;测试文件(spec/**/*.lua、**/*_test.lua)使用ngx_lua+busted标准,这与仓库使用 busted 作为测试框架一致; - 白名单全局变量仅三个:
_KONG、kong、ngx.IS_CLI——从源码结构看,这反映了 Kong 代码中合法的全局入口; not_globals禁用了string.len、table.getn等已被 Lua 5.1+ 淘汰的函数;ignore = { "6." }忽略了空白类告警(风格由代码规范而非 linter 强制);- 对个别文件单独放宽
read_globals,例如 kong/tools/sandbox/kong.lua 允许table.pack/table.unpack,kong/plugins/ldap-auth/*.lua允许bit.mod、string.pack/string.unpack——因为 LuaJIT 的 5.1 语义下这些 API 并非总是存在。
六、编写测试:busted 三大套件
Kong 使用 busted 编写测试。CONTRIBUTING.md 要求:你的补丁必须包含相应测试套件的更新或新增。仓库的测试分为三大套件(对应 spec/ 下的目录编号):
| 目录 | 内容 | Makefile 目标 |
|---|---|---|
spec/01-unit |
单元测试(测试某个 Lua 模块或函数) | make test |
spec/02-integration |
集成测试:启动 Kong(连接运行中的数据库),执行 Admin API 与代理请求并验证输出 | make test-integration |
spec/03-plugins |
内置插件的测试(单元测试 + 集成测试) | make test-plugins |
spec/(全部) |
一次性运行所有套件 | make test-all |
Makefile 中这些目标实际调用 bin/busted(由 dev 目标通过 LuaRocks 安装的 busted 2.2.0 等开发依赖提供);另有一个未列入文档的贡献者利器 make test-custom test_spec=<path>,可单独运行某个 spec 文件,适合在开发循环中快速验证。
写测试的几条守则:
- 使用合适的
describe/it块,让被测对象一目了然; - 保持测试原子性:一个测试不应同时断言两个无关行为;
- 涉及数据层的测试要针对所有支持的数据库运行。
关于类型断言,官方给出了明确的“好/坏”对照:
-- bad
assert.Nil(foo)
assert.True(bar)
-- good
assert.is_nil(foo)
assert.is_true(bar)
比较 table 时:
-- bad (most of the time)
assert.equal(t1, t2)
-- good
assert.same(t1, t2)
除 busted 套件外,仓库还有一类基于 prove/TAP 的测试:t/ 目录下的 .t 文件(如 t/05-mlcache/ 中针对 resty.mlcache 的 15 个用例,t/01-pdk/ 中按 PDK 模块组织的用例)。Makefile 中的 pdk-phase-checks 目标会运行 t/01*/*/00-phase*.t 并生成 luacov 报告,用于验证 PDK 各接口在 Nginx 生命周期各阶段(rewrite/access/log 等)的调用合法性——这是贡献 PDK 相关代码时值得了解的验证机制。测试环境方面,spec/README.md 说明测试实例会忽略 KONG_xxx 环境变量以保证确定性,需用 KONG_TEST_xxx 形式覆盖;集成/插件测试可编辑 spec/kong_tests.conf 指向你自己的 Postgres/Cassandra 实例(详见 DEVELOPER.md)。
七、编写 Changelog:YAML 条目制
CONTRIBUTING.md 要求 PR 附带 changelog 条目,并指向 changelog/README.md。从仓库实际结构看,这套机制的工作方式是:
- 每个 PR 在
changelog/unreleased/kong/下新增一个.yml文件(当前仓库该目录下已有 77 个待发版的条目); - 文件字段遵循 changelog/changelog-template.yaml:
message: # "Description of your change" (required)
type: # One of "feature", "bugfix", "dependency", "deprecation", "breaking_change", "performance" (required)
scope: # One of "Core", "Plugin", "PDK", "Admin API", "Performance", "Configuration", "Clustering", "Portal", "CLI Command" (optional)
- 一个真实条目(来自
changelog/unreleased/kong/)长这样:
message: |
**ai**: Added support for boto3 SDKs for the Bedrock provider, and for Google GenAI SDKs for the Gemini provider.
type: "feature"
scope: "Plugin"
- 发布时,
changelog/下按版本归档了各版本的条目目录(如changelog/3.5.0/、changelog/3.6.0/直至changelog/3.9.0/),changelog/Makefile 负责汇总生成正式 changelog PR,changelog/verify-prs 脚本则用于在两个版本间校验“每个功能 PR 是否都有对应 changelog 文件”。
这也解释了 PR 检查清单中“不要手动更新 CHANGELOG.md”的原因:CHANGELOG.md 是由这套 YAML 条目在发布流程中自动生成的产物。
八、编写高性能代码:面向 LuaJIT 的最佳实践
Kong 运行在 LuaJIT 上,而不是 Lua-PUC。CONTRIBUTING.md 据此给出了一套 LuaJIT 性能守则:
- 不要创建全局变量;
- 热路径上不要使用 NYI(Not Yet Implemented)函数;
- 优先使用 FFI,而不是传统的 Lua C API 绑定;
- 尽量预分配 table 槽位以避免 rehash:
-- bad
local t = {}
for i = 1, 100 do
t[i] = i
end
-- good
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
t[i] = i
end
- 缓存热路径用到的全局变量,缓存名用原名中的
.换成_;对 OpenResty 内建 API,局部化时可以省掉ngx.前缀:
-- bad
for i = 1, 100 do
t[i] = math.random()
end
-- good
local math_random = math.random
for i = 1, 100 do
t[i] = math_random()
end
local req_get_post_args = ngx.req.get_post_args
非热路径可以不做局部化,例如错误分支中的 ngx.log(ngx.ERR, ...) 就没必要缓存。
- 缓存 table 的长度和下标,避免不必要的 CPU 周期:
-- bad
for i = 1, 100 do
t[#t + 1] = other_tab[#other_tab]
end
-- good
local n = 0
local n_other_tab = #other_tab
for i = 1, 100 do
n = n + 1
t[n] = other_tab[n_other_tab]
end
文档最后强调:最重要的仍然是设计一个高效的算法——再多的语言级技巧,也弥补不了糟糕的算法。从源码结构看,这些建议在仓库中是被实际执行的:kong/tools/table.lua 提供了 table 工具函数,kong/resty/mlcache/ 与 kong/runloop/ 等热路径代码普遍采用局部化缓存模式,可作为风格参照。
九、Contributor Badge
如果你的 PR 被接受(修复 bug、增加功能,或显著改善 Kong 的使用/理解),你将符合领取数字 Contributor Badge 的条件,可填写 Contributors Submissions form 申请。徽章有效期 1 年,到期后可通过提交新的贡献续期。
十、Lua 代码风格规范
为保证代码库健康一致,CONTRIBUTING.md 给出了一套(非穷尽的)Lua 风格约定。这套风格明显承袭 OpenResty 与 Nginx 的社区习惯,总则只有两条:行宽不超过 80 字符;缩进使用 2 个空格。遇到不确定的写法,去代码库里找类似案例保持一致;仓库中确实存在不满足规范的历史代码,欢迎贡献把它们改到推荐风格。
模块(Modules)
模块内逻辑块之间用两行空行分隔:
local foo = require "kong.foo"
local _M = {}
function _M.bar()
-- do thing...
end
function _M.baz()
-- do thing...
end
return _M
变量(Variables)
命名使用 snake_case;常量使用大写:
-- bad
local myString = "hello world"
-- good
local my_string = "hello world"
-- bad
local max_len = 100
-- good
local MAX_LEN = 100
表(Tables)
使用构造器语法,并带尾逗号:
-- bad
local t = {}
t.foo = "hello"
t.bar = "world"
-- good
local t = {
foo = "hello",
bar = "world", -- note the trailing comma
}
单行构造器中,花括号和赋值两侧要有空格:
-- bad
local t = {foo="hello",bar="world"}
-- good
local t = { foo = "hello", bar = "world" }
遍历数组优先用 ipairs() 而非手写 for i = 1, #t,可读性更好:
-- bad
for i = 1, #t do
...
end
-- good
for _, v in ipairs(t) do
...
end
字符串(Strings)
- 一律优先使用双引号(普通文件和
*_by_lua_block指令内都是如此):
-- bad
local str = 'hello'
-- good
local str = "hello"
- 字符串内含双引号时,优先用长括号字符串:
-- bad
local str = "message: \"hello\""
-- good
local str = [[message: "hello"]]
- 拼接操作符
..两侧要有空格:
-- bad
local str = "hello ".."world"
-- good
local str = "hello " .. "world"
- 过长字符串要换行,并用
..连接:
-- bad
local str = "It is a very very very long string, that should be broken into multiple lines."
-- good
local str = "It is a very very very long string, " ..
"that should be broken into multiple lines."
函数(Functions)
优先使用函数语法而非变量语法:
-- bad
local foo = function()
end
-- good
local function foo()
end
尽早校验、尽早返回:
-- bad
local function check_name(name)
local valid = #name > 3
valid = valid and #name < 30
-- other validations
return valid
end
-- good
local function check_name(name)
if #name <= 3 or #name >= 30 then
return false
end
-- other validations
return true
end
遵循 Lua 的多返回值错误约定:可恢复错误用 nil + 错误描述字符串返回:
-- bad
local function check()
local ok, err = do_thing()
if not ok then
return false, { message = err }
end
return true
end
-- good
local function check()
local ok, err = do_thing()
if not ok then
return nil, "could not do thing: " .. err
end
return true
end
函数调用导致行宽超过 80 字符时,溢出的参数对齐到第一个参数:
-- bad
local str = string.format("SELECT * FROM users WHERE first_name = '%s'", first_name)
-- good
local str = string.format("SELECT * FROM users WHERE first_name = '%s'",
first_name)
条件表达式(Conditional expressions)
避免单行条件,缩进子分支:
-- bad
if err then return nil, err end
-- good
if err then
return nil, err
end
判断“是否赋值”时使用简写,除非你关心 nil 与 false 的区别:
-- bad
if str ~= nil then
end
-- good
if str then
end
多分支且分支跨多行时,elseif 和 else 上方必须留空行;单行分支则不需要:
-- bad
if foo then
do_stuff()
keep_doing_stuff()
elseif bar then
do_other_stuff()
keep_doing_other_stuff()
else
error()
end
-- good
if thing then
do_stuff()
keep_doing_stuff()
elseif bar then
do_other_stuff()
keep_doing_other_stuff()
else
error()
end
--- good
if foo then
do_stuff()
else
error("failed!")
end
注意:若某分支较长,则所有分支(包括单行 else)都在其上方留空行。
当分支已经 return 时,不要再写后续分支,把其余逻辑写到父层级:
-- bad
if not str then
return nil, "bad value"
else
do_thing(str)
end
-- good
if not str then
return nil, "bad value"
end
do_thing(str)
赋值或返回时,能用三元表达式提升可读性就用:
-- bad
local foo
if bar then
foo = "hello"
else
foo = "world"
end
-- good
local foo = bar and "hello" or "world"
表达式超宽时,续行对齐表达式开头:
-- bad
if thing_one < 1 and long_and_complicated_function(arg1, arg2) < 10 or thing_two > 10 then
end
-- good
if thing_one < 1 and long_and_complicated_function(arg1, arg2) < 10
or thing_two > 10
then
end
调用 ngx.log() 传入变量时,优先用 vararg 风格,而不是 .. 拼接(后者在变量为 nil 时会抛异常):
-- bad
ngx.log(ngx.DEBUG, "if `my_var` is nil, this code throws an exception: " .. my_var)
-- good
ngx.log(ngx.DEBUG, "if `my_var` is nil, this code is fine: ", my_var)
十一、贡献前速查清单
把上述要点压缩成一张提交前 checklist:
- 分支名符合
feat/fix/tests/refactor/style/docs/chore/perf前缀约定; - 每个 commit 原子、message 为
type(scope): subject格式,标题 ≤50 字符、body 每行 ≤72 字符、现在时、小写、无句号; make lint(Luacheck + 调试标记检查)通过;make test/make test-integration/make test-plugins按变更范围通过,且补丁包含 busted 测试(断言用assert.is_nil/assert.same等规范写法);- 在
changelog/unreleased/kong/下按模板提交 YAML changelog 条目,且不动CHANGELOG.md; - 热路径代码遵循 LuaJIT 守则(无全局变量、局部化缓存、预分配 table);
- 代码风格符合 80 列 / 2 空格缩进及本文第十节的各细则;
- 已 rebase 到基线分支,提交历史线性干净。
对于想深入了解开发环境搭建的读者,DEVELOPER.md 描述了从源码构建开发环境(make dev、Bazel venv、start_services 拉起数据库、kong migrations bootstrap 与 kong start)的完整流程,本文的 lint 与测试目标均可在该环境下直接运行。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00