- 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>
280 lines
8.4 KiB
Lua
280 lines
8.4 KiB
Lua
--
|
|
-- 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.
|
|
--
|
|
local core = require("apisix.core")
|
|
local config_util = require("apisix.core.config_util")
|
|
local pb = require("pb")
|
|
local protoc = require("protoc")
|
|
local pcall = pcall
|
|
local ipairs = ipairs
|
|
local decode_base64 = ngx.decode_base64
|
|
|
|
|
|
local protos
|
|
local lrucache_proto = core.lrucache.new({
|
|
ttl = 300, count = 100
|
|
})
|
|
|
|
local proto_fake_file = "filename for loaded"
|
|
|
|
local function compile_proto_text(content)
|
|
protoc.reload()
|
|
local _p = protoc.new()
|
|
-- the loaded proto won't appears in _p.loaded without a file name after lua-protobuf=0.3.2,
|
|
-- which means _p.loaded after _p:load(content) is always empty, so we can pass a fake file
|
|
-- name to keep the code below unchanged, or we can create our own load function with returning
|
|
-- the loaded DescriptorProto table additionally, see more details in
|
|
-- https://github.com/apache/apisix/pull/4368
|
|
local ok, res = pcall(_p.load, _p, content, proto_fake_file)
|
|
if not ok then
|
|
return nil, res
|
|
end
|
|
|
|
if not res or not _p.loaded then
|
|
return nil, "failed to load proto content"
|
|
end
|
|
|
|
local compiled = _p.loaded
|
|
|
|
local index = {}
|
|
for _, s in ipairs(compiled[proto_fake_file].service or {}) do
|
|
local method_index = {}
|
|
for _, m in ipairs(s.method) do
|
|
method_index[m.name] = m
|
|
end
|
|
|
|
index[compiled[proto_fake_file].package .. '.' .. s.name] = method_index
|
|
end
|
|
|
|
compiled[proto_fake_file].index = index
|
|
|
|
return compiled
|
|
end
|
|
|
|
|
|
local function compile_proto_bin(content)
|
|
content = decode_base64(content)
|
|
if not content then
|
|
return nil
|
|
end
|
|
|
|
-- pb.load doesn't return err
|
|
local ok = pb.load(content)
|
|
if not ok then
|
|
return nil
|
|
end
|
|
|
|
local files = pb.decode("google.protobuf.FileDescriptorSet", content).file
|
|
local index = {}
|
|
for _, f in ipairs(files) do
|
|
for _, s in ipairs(f.service or {}) do
|
|
local method_index = {}
|
|
for _, m in ipairs(s.method) do
|
|
method_index[m.name] = m
|
|
end
|
|
|
|
index[f.package .. '.' .. s.name] = method_index
|
|
end
|
|
end
|
|
|
|
local compiled = {}
|
|
compiled[proto_fake_file] = {}
|
|
compiled[proto_fake_file].index = index
|
|
return compiled
|
|
end
|
|
|
|
|
|
local function compile_proto(content)
|
|
-- clear pb state
|
|
local old_pb_state = pb.state(nil)
|
|
|
|
local compiled, err = compile_proto_text(content)
|
|
if not compiled then
|
|
compiled = compile_proto_bin(content)
|
|
if not compiled then
|
|
return nil, err
|
|
end
|
|
end
|
|
|
|
-- fetch pb state
|
|
compiled.pb_state = pb.state(old_pb_state)
|
|
return compiled
|
|
end
|
|
|
|
|
|
local _M = {
|
|
version = 0.1,
|
|
compile_proto = compile_proto,
|
|
proto_fake_file = proto_fake_file
|
|
}
|
|
|
|
local function create_proto_obj(proto_id)
|
|
if protos.values == nil then
|
|
return nil
|
|
end
|
|
|
|
local content
|
|
for _, proto in config_util.iterate_values(protos.values) do
|
|
if proto_id == proto.value.id then
|
|
content = proto.value.content
|
|
break
|
|
end
|
|
end
|
|
|
|
if not content then
|
|
return nil, "failed to find proto by id: " .. proto_id
|
|
end
|
|
|
|
return compile_proto(content)
|
|
end
|
|
|
|
|
|
function _M.fetch(proto_id)
|
|
return lrucache_proto(proto_id, protos.conf_version,
|
|
create_proto_obj, proto_id)
|
|
end
|
|
|
|
|
|
function _M.protos()
|
|
if not protos then
|
|
return nil, nil
|
|
end
|
|
|
|
return protos.values, protos.conf_version
|
|
end
|
|
|
|
|
|
local grpc_status_proto = [[
|
|
syntax = "proto3";
|
|
|
|
package grpc_status;
|
|
|
|
message Any {
|
|
// A URL/resource name that uniquely identifies the type of the serialized
|
|
// protocol buffer message. This string must contain at least
|
|
// one "/" character. The last segment of the URL's path must represent
|
|
// the fully qualified name of the type (as in
|
|
// `path/google.protobuf.Duration`). The name should be in a canonical form
|
|
// (e.g., leading "." is not accepted).
|
|
//
|
|
// In practice, teams usually precompile into the binary all types that they
|
|
// expect it to use in the context of Any. However, for URLs which use the
|
|
// scheme `http`, `https`, or no scheme, one can optionally set up a type
|
|
// server that maps type URLs to message definitions as follows:
|
|
//
|
|
// * If no scheme is provided, `https` is assumed.
|
|
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
|
// value in binary format, or produce an error.
|
|
// * Applications are allowed to cache lookup results based on the
|
|
// URL, or have them precompiled into a binary to avoid any
|
|
// lookup. Therefore, binary compatibility needs to be preserved
|
|
// on changes to types. (Use versioned type names to manage
|
|
// breaking changes.)
|
|
//
|
|
// Note: this functionality is not currently available in the official
|
|
// protobuf release, and it is not used for type URLs beginning with
|
|
// type.googleapis.com.
|
|
//
|
|
// Schemes other than `http`, `https` (or the empty scheme) might be
|
|
// used with implementation specific semantics.
|
|
//
|
|
string type_url = 1;
|
|
|
|
// Must be a valid serialized protocol buffer of the above specified type.
|
|
bytes value = 2;
|
|
}
|
|
|
|
// The `Status` type defines a logical error model that is suitable for
|
|
// different programming environments, including REST APIs and RPC APIs. It is
|
|
// used by [gRPC](https://github.com/grpc). Each `Status` message contains
|
|
// three pieces of data: error code, error message, and error details.
|
|
//
|
|
// You can find out more about this error model and how to work with it in the
|
|
// [API Design Guide](https://cloud.google.com/apis/design/errors).
|
|
message ErrorStatus {
|
|
// The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].
|
|
int32 code = 1;
|
|
|
|
// A developer-facing error message, which should be in English. Any
|
|
// user-facing error message should be localized and sent in the
|
|
// [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.
|
|
string message = 2;
|
|
|
|
// A list of messages that carry the error details. There is a common set of
|
|
// message types for APIs to use.
|
|
repeated Any details = 3;
|
|
}
|
|
]]
|
|
|
|
|
|
local status_pb_state
|
|
local function init_status_pb_state()
|
|
if not status_pb_state then
|
|
-- clear current pb state
|
|
local old_pb_state = pb.state(nil)
|
|
|
|
-- initialize protoc compiler
|
|
protoc.reload()
|
|
local status_protoc = protoc.new()
|
|
-- do not use loadfile here, it can not load the proto file when using a relative address
|
|
-- after luarocks install apisix
|
|
local ok, err = status_protoc:load(grpc_status_proto, "grpc_status.proto")
|
|
if not ok then
|
|
status_protoc:reset()
|
|
pb.state(old_pb_state)
|
|
return "failed to load grpc status protocol: " .. err
|
|
end
|
|
|
|
status_pb_state = pb.state(old_pb_state)
|
|
end
|
|
end
|
|
|
|
|
|
function _M.fetch_status_pb_state()
|
|
return status_pb_state
|
|
end
|
|
|
|
|
|
function _M.init()
|
|
local err
|
|
protos, err = core.config.new("/protos", {
|
|
automatic = true,
|
|
item_schema = core.schema.proto
|
|
})
|
|
if not protos then
|
|
core.log.error("failed to create etcd instance for fetching protos: ",
|
|
err)
|
|
return
|
|
end
|
|
|
|
if not status_pb_state then
|
|
err = init_status_pb_state()
|
|
if err then
|
|
core.log.error("failed to init grpc status proto: ",
|
|
err)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
function _M.destroy()
|
|
if protos then
|
|
protos:close()
|
|
end
|
|
end
|
|
|
|
return _M
|