Files
ReachableCEO 54cc5f7308 feat(apisix): add Cloudron package
- Implements Apache APISIX packaging for Cloudron platform.
- Includes Dockerfile, CloudronManifest.json, and start.sh.
- Configured to use Cloudron's etcd addon.

🤖 Generated with Gemini CLI
Co-Authored-By: Gemini <noreply@google.com>
2025-09-04 09:42:47 -05:00

622 lines
19 KiB
Perl

#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
use t::APISIX 'no_plan';
no_long_string();
no_shuffle();
no_root_location();
add_block_preprocessor(sub {
my ($block) = @_;
if (!$block->request) {
$block->set_value("request", "GET /t");
}
});
run_tests;
__DATA__
=== TEST 1: set rule
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayMultipleHello(MultipleHelloRequest) returns (MultipleHelloReply) {}
}
enum Gender {
GENDER_UNKNOWN = 0;
GENDER_MALE = 1;
GENDER_FEMALE = 2;
}
message Person {
string name = 1;
int32 age = 2;
}
message MultipleHelloRequest {
string name = 1;
repeated string items = 2;
repeated Gender genders = 3;
repeated Person persons = 4;
}
message MultipleHelloReply{
string message = 1;
}"
}]]
)
if code >= 300 then
ngx.say(body)
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayMultipleHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.say(body)
return
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 2: hit route
--- request
POST /grpctest
{"name":"world","persons":[{"name":"Joe","age":1},{"name":"Jake","age":2}]}
--- more_headers
Content-Type: application/json
--- response_body chomp
{"message":"Hello world, name: Joe, age: 1, name: Jake, age: 2"}
=== TEST 3: set proto (id: 1, get error response from rpc)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/protos/1',
ngx.HTTP_PUT,
[[{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc GetErrResp (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
repeated string items = 2;
}
message HelloReply {
string message = 1;
repeated string items = 2;
}
message ErrorDetail {
int64 code = 1;
string message = 2;
string type = 3;
}"
}]]
)
if code >= 300 then
ngx.status = code
return
end
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 4: hit route (error response in header)
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body, headers = t('/grpctest?name=world',
ngx.HTTP_GET
)
ngx.status = code
ngx.header['grpc-status'] = headers['grpc-status']
ngx.header['grpc-message'] = headers['grpc-message']
ngx.header['grpc-status-details-bin'] = headers['grpc-status-details-bin']
body = json.encode(body)
ngx.say(body)
}
}
--- response_headers
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
--- response_body_unlike eval
qr/error/
--- error_code: 503
=== TEST 5: set routes (id: 1, show error response in body)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 6: hit route (show error status in body)
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body, headers = t('/grpctest?name=world',
ngx.HTTP_GET
)
ngx.status = code
ngx.header['grpc-status'] = headers['grpc-status']
ngx.header['grpc-message'] = headers['grpc-message']
ngx.header['grpc-status-details-bin'] = headers['grpc-status-details-bin']
body = json.decode(body)
body = json.encode(body)
ngx.say(body)
}
}
--- response_headers
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
--- response_body
{"error":{"code":14,"details":[{"type_url":"type.googleapis.com/helloworld.ErrorDetail","value":"\b\u0001\u0012\u001cThe server is out of service\u001a\u0007service"}],"message":"Out of service"}}
--- error_code: 503
=== TEST 7: set routes (id: 1, show error details in body)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true,
"status_detail_type": "helloworld.ErrorDetail"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 8: hit route (show error details in body)
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body, headers = t('/grpctest?name=world',
ngx.HTTP_GET
)
ngx.status = code
ngx.header['grpc-status'] = headers['grpc-status']
ngx.header['grpc-message'] = headers['grpc-message']
ngx.header['grpc-status-details-bin'] = headers['grpc-status-details-bin']
body = json.decode(body)
body = json.encode(body)
ngx.say(body)
}
}
--- response_headers
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
--- response_body
{"error":{"code":14,"details":[{"code":1,"message":"The server is out of service","type":"service"}],"message":"Out of service"}}
--- error_code: 503
=== TEST 9: set routes (id: 1, show error details in body and wrong status_detail_type)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET", "POST"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true,
"status_detail_type": "helloworld.error"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 10: hit route (show error details in body and wrong status_detail_type)
--- config
location /t {
content_by_lua_block {
local json = require("toolkit.json")
local t = require("lib.test_admin").test
local code, body, headers = t('/grpctest?name=world',
ngx.HTTP_GET
)
ngx.status = code
ngx.header['grpc-status'] = headers['grpc-status']
ngx.header['grpc-message'] = headers['grpc-message']
ngx.header['grpc-status-details-bin'] = headers['grpc-status-details-bin']
ngx.say(body)
}
}
--- response_headers
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
--- response_body
failed to call pb.decode to decode details in grpc-status-details-bin
--- error_log
transform response error: failed to call pb.decode to decode details in grpc-status-details-bin, err:
--- error_code: 503
=== TEST 11: set binary rule for EchoStruct
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local json = require("toolkit.json")
local content = t.read_file("t/grpc_server_example/echo.pb")
local data = {content = ngx.encode_base64(content)}
local code, body = t.test('/apisix/admin/protos/1',
ngx.HTTP_PUT,
json.encode(data)
)
if code >= 300 then
ngx.status = code
ngx.say(body)
return
end
local code, body = t.test('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "echo.Echo",
"method": "EchoStruct"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:10051": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 12: hit route to test EchoStruct
--- config
location /t {
content_by_lua_block {
local core = require "apisix.core"
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/grpctest"
local body = [[{"data":{"fields":{"foo":{"string_value":"xxx"},"bar":{"number_value":666}}}}]]
local opt = {method = "POST", body = body, headers = {["Content-Type"] = "application/json"}, keepalive = false}
local httpc = http.new()
local res, err = httpc:request_uri(uri, opt)
if not res then
ngx.log(ngx.ERR, err)
return ngx.exit(500)
end
if res.status > 300 then
return ngx.exit(res.status)
else
local req = core.json.decode(body)
local rsp = core.json.decode(res.body)
for k, v in pairs(req.data.fields) do
if rsp.data.fields[k] == nil then
ngx.log(ngx.ERR, "rsp missing field=", k, ", rsp: ", res.body)
else
for k1, v1 in pairs(v) do
if v1 ~= rsp.data.fields[k][k1] then
ngx.log(ngx.ERR, "rsp mismatch: k=", k1,
", req=", v1, ", rsp=", rsp.data.fields[k][k1])
end
end
end
end
end
}
}
=== TEST 13: bugfix - filter out illegal INT(string) formats
--- config
location /t {
content_by_lua_block {
local pcall = pcall
local require = require
local protoc = require("protoc")
local pb = require("pb")
local pb_encode = pb.encode
assert(protoc:load [[
syntax = "proto3";
message IntStringPattern {
int64 value = 1;
}]])
local patterns
do
local function G(pattern)
return {pattern, true}
end
local function B(pattern)
return {pattern, [[bad argument #2 to '?' (number/'#number' expected for field 'value', got string)]]}
end
patterns = {
G(1), G(2), G(-3), G("#123"), G("0xabF"), G("#-0x123abcdef"), G("-#0x123abcdef"), G("#0x123abcdef"), G("123"),
B("#a"), B("+aaa"), B("#aaaa"), B("#-aa"),
}
end
for _, p in pairs(patterns) do
local pattern = {
value = p[1],
}
local status, err = pcall(pb_encode, "IntStringPattern", pattern)
local res = status
if not res then
res = err
end
assert(res == p[2])
end
ngx.say("passed")
}
}
--- response_body
passed
=== TEST 14: pb_option - check the matchings between enum and category
--- config
location /t {
content_by_lua_block {
local ngx_re_match = ngx.re.match
local plugin = require("apisix.plugins.grpc-transcode")
local pb_option_def = plugin.schema.properties.pb_option.items.anyOf
local patterns = {
[[^enum_as_.+$]],
[[^int64_as_.+$]],
[[^.+_default_.+$]],
[[^.+_hooks$]],
}
local function check_pb_option_enum_category()
for i, category in ipairs(pb_option_def) do
for _, enum in ipairs(category.enum) do
if not ngx_re_match(enum, patterns[i], "jo") then
return ([[mismatch between enum("%s") and category("%s")]]):format(
enum, category.description)
end
end
end
end
local err = check_pb_option_enum_category()
if err then
ngx.say(err)
return
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done