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>
This commit is contained in:
334
CloudronPackages/APISIX/apisix-source/apisix/consumer.lua
Normal file
334
CloudronPackages/APISIX/apisix-source/apisix/consumer.lua
Normal file
@@ -0,0 +1,334 @@
|
||||
--
|
||||
-- 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_local = require("apisix.core.config_local")
|
||||
local secret = require("apisix.secret")
|
||||
local plugin = require("apisix.plugin")
|
||||
local plugin_checker = require("apisix.plugin").plugin_checker
|
||||
local check_schema = require("apisix.core.schema").check
|
||||
local error = error
|
||||
local ipairs = ipairs
|
||||
local pairs = pairs
|
||||
local type = type
|
||||
local string_sub = string.sub
|
||||
local consumers
|
||||
|
||||
|
||||
local _M = {
|
||||
version = 0.3,
|
||||
}
|
||||
|
||||
local lrucache = core.lrucache.new({
|
||||
ttl = 300, count = 512
|
||||
})
|
||||
|
||||
-- Please calculate and set the value of the "consumers_count_for_lrucache"
|
||||
-- variable based on the number of consumers in the current environment,
|
||||
-- taking into account the appropriate adjustment coefficient.
|
||||
local consumers_count_for_lrucache = 4096
|
||||
|
||||
local function remove_etcd_prefix(key)
|
||||
local prefix = ""
|
||||
local local_conf = config_local.local_conf()
|
||||
local role = core.table.try_read_attr(local_conf, "deployment", "role")
|
||||
local provider = core.table.try_read_attr(local_conf, "deployment", "role_" ..
|
||||
role, "config_provider")
|
||||
if provider == "etcd" and local_conf.etcd and local_conf.etcd.prefix then
|
||||
prefix = local_conf.etcd.prefix
|
||||
end
|
||||
return string_sub(key, #prefix + 1)
|
||||
end
|
||||
|
||||
-- /{etcd.prefix}/consumers/{consumer_name}/credentials/{credential_id} --> {consumer_name}
|
||||
local function get_consumer_name_from_credential_etcd_key(key)
|
||||
local uri_segs = core.utils.split_uri(remove_etcd_prefix(key))
|
||||
return uri_segs[3]
|
||||
end
|
||||
|
||||
local function is_credential_etcd_key(key)
|
||||
if not key then
|
||||
return false
|
||||
end
|
||||
|
||||
local uri_segs = core.utils.split_uri(remove_etcd_prefix(key))
|
||||
return uri_segs[2] == "consumers" and uri_segs[4] == "credentials"
|
||||
end
|
||||
|
||||
local function get_credential_id_from_etcd_key(key)
|
||||
local uri_segs = core.utils.split_uri(remove_etcd_prefix(key))
|
||||
return uri_segs[5]
|
||||
end
|
||||
|
||||
local function filter_consumers_list(data_list)
|
||||
if #data_list == 0 then
|
||||
return data_list
|
||||
end
|
||||
|
||||
local list = {}
|
||||
for _, item in ipairs(data_list) do
|
||||
if not (type(item) == "table" and is_credential_etcd_key(item.key)) then
|
||||
core.table.insert(list, item)
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
local plugin_consumer
|
||||
do
|
||||
local consumers_id_lrucache = core.lrucache.new({
|
||||
count = consumers_count_for_lrucache
|
||||
})
|
||||
|
||||
local function construct_consumer_data(val, plugin_config)
|
||||
-- if the val is a Consumer, clone it to the local consumer;
|
||||
-- if the val is a Credential, to get the Consumer by consumer_name and then clone
|
||||
-- it to the local consumer.
|
||||
local consumer
|
||||
if is_credential_etcd_key(val.key) then
|
||||
local consumer_name = get_consumer_name_from_credential_etcd_key(val.key)
|
||||
local the_consumer = consumers:get(consumer_name)
|
||||
if the_consumer and the_consumer.value then
|
||||
consumer = core.table.clone(the_consumer.value)
|
||||
consumer.modifiedIndex = the_consumer.modifiedIndex
|
||||
consumer.credential_id = get_credential_id_from_etcd_key(val.key)
|
||||
else
|
||||
-- Normally wouldn't get here:
|
||||
-- it should belong to a consumer for any credential.
|
||||
core.log.error("failed to get the consumer for the credential,",
|
||||
" a wild credential has appeared!",
|
||||
" credential key: ", val.key, ", consumer name: ", consumer_name)
|
||||
return nil, "failed to get the consumer for the credential"
|
||||
end
|
||||
else
|
||||
consumer = core.table.clone(val.value)
|
||||
consumer.modifiedIndex = val.modifiedIndex
|
||||
end
|
||||
|
||||
-- if the consumer has labels, set the field custom_id to it.
|
||||
-- the custom_id is used to set in the request headers to the upstream.
|
||||
if consumer.labels then
|
||||
consumer.custom_id = consumer.labels["custom_id"]
|
||||
end
|
||||
|
||||
-- Note: the id here is the key of consumer data, which
|
||||
-- is 'username' field in admin
|
||||
consumer.consumer_name = consumer.id
|
||||
consumer.auth_conf = plugin_config
|
||||
|
||||
return consumer
|
||||
end
|
||||
|
||||
|
||||
function plugin_consumer()
|
||||
local plugins = {}
|
||||
|
||||
if consumers.values == nil then
|
||||
return plugins
|
||||
end
|
||||
|
||||
-- consumers.values is the list that got from etcd by prefix key {etcd_prefix}/consumers.
|
||||
-- So it contains consumers and credentials.
|
||||
-- The val in the for-loop may be a Consumer or a Credential.
|
||||
for _, val in ipairs(consumers.values) do
|
||||
if type(val) ~= "table" then
|
||||
goto CONTINUE
|
||||
end
|
||||
|
||||
for name, config in pairs(val.value.plugins or {}) do
|
||||
local plugin_obj = plugin.get(name)
|
||||
if plugin_obj and plugin_obj.type == "auth" then
|
||||
if not plugins[name] then
|
||||
plugins[name] = {
|
||||
nodes = {},
|
||||
len = 0,
|
||||
conf_version = consumers.conf_version
|
||||
}
|
||||
end
|
||||
|
||||
local consumer = consumers_id_lrucache(val.value.id .. name,
|
||||
val.modifiedIndex, construct_consumer_data, val, config)
|
||||
if consumer == nil then
|
||||
goto CONTINUE
|
||||
end
|
||||
|
||||
plugins[name].len = plugins[name].len + 1
|
||||
core.table.insert(plugins[name].nodes, plugins[name].len,
|
||||
consumer)
|
||||
core.log.info("consumer:", core.json.delay_encode(consumer))
|
||||
end
|
||||
end
|
||||
|
||||
::CONTINUE::
|
||||
end
|
||||
|
||||
return plugins
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
_M.filter_consumers_list = filter_consumers_list
|
||||
|
||||
function _M.get_consumer_key_from_credential_key(key)
|
||||
local uri_segs = core.utils.split_uri(key)
|
||||
return "/consumers/" .. uri_segs[3]
|
||||
end
|
||||
|
||||
function _M.plugin(plugin_name)
|
||||
local plugin_conf = core.lrucache.global("/consumers",
|
||||
consumers.conf_version, plugin_consumer)
|
||||
return plugin_conf[plugin_name]
|
||||
end
|
||||
|
||||
function _M.consumers_conf(plugin_name)
|
||||
return _M.plugin(plugin_name)
|
||||
end
|
||||
|
||||
|
||||
-- attach chosen consumer to the ctx, used in auth plugin
|
||||
function _M.attach_consumer(ctx, consumer, conf)
|
||||
ctx.consumer = consumer
|
||||
ctx.consumer_name = consumer.consumer_name
|
||||
ctx.consumer_group_id = consumer.group_id
|
||||
ctx.consumer_ver = conf.conf_version
|
||||
|
||||
core.request.set_header(ctx, "X-Consumer-Username", consumer.username)
|
||||
core.request.set_header(ctx, "X-Credential-Identifier", consumer.credential_id)
|
||||
core.request.set_header(ctx, "X-Consumer-Custom-ID", consumer.custom_id)
|
||||
end
|
||||
|
||||
|
||||
function _M.consumers()
|
||||
if not consumers then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
return filter_consumers_list(consumers.values), consumers.conf_version
|
||||
end
|
||||
|
||||
|
||||
local create_consume_cache
|
||||
do
|
||||
local consumer_lrucache = core.lrucache.new({
|
||||
count = consumers_count_for_lrucache
|
||||
})
|
||||
|
||||
local function fill_consumer_secret(consumer)
|
||||
local new_consumer = core.table.clone(consumer)
|
||||
new_consumer.auth_conf = secret.fetch_secrets(new_consumer.auth_conf, false)
|
||||
return new_consumer
|
||||
end
|
||||
|
||||
|
||||
function create_consume_cache(consumers_conf, key_attr)
|
||||
local consumer_names = {}
|
||||
|
||||
for _, consumer in ipairs(consumers_conf.nodes) do
|
||||
core.log.info("consumer node: ", core.json.delay_encode(consumer))
|
||||
local new_consumer = consumer_lrucache(consumer, nil,
|
||||
fill_consumer_secret, consumer)
|
||||
consumer_names[new_consumer.auth_conf[key_attr]] = new_consumer
|
||||
end
|
||||
|
||||
return consumer_names
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
function _M.consumers_kv(plugin_name, consumer_conf, key_attr)
|
||||
local consumers = lrucache("consumers_key#" .. plugin_name, consumer_conf.conf_version,
|
||||
create_consume_cache, consumer_conf, key_attr)
|
||||
|
||||
return consumers
|
||||
end
|
||||
|
||||
|
||||
function _M.find_consumer(plugin_name, key, key_value)
|
||||
local consumer
|
||||
local consumer_conf
|
||||
consumer_conf = _M.plugin(plugin_name)
|
||||
if not consumer_conf then
|
||||
return nil, nil, "Missing related consumer"
|
||||
end
|
||||
local consumers = _M.consumers_kv(plugin_name, consumer_conf, key)
|
||||
consumer = consumers[key_value]
|
||||
return consumer, consumer_conf
|
||||
end
|
||||
|
||||
|
||||
local function check_consumer(consumer, key)
|
||||
local data_valid
|
||||
local err
|
||||
if is_credential_etcd_key(key) then
|
||||
data_valid, err = check_schema(core.schema.credential, consumer)
|
||||
else
|
||||
data_valid, err = check_schema(core.schema.consumer, consumer)
|
||||
end
|
||||
if not data_valid then
|
||||
return data_valid, err
|
||||
end
|
||||
|
||||
return plugin_checker(consumer, core.schema.TYPE_CONSUMER)
|
||||
end
|
||||
|
||||
|
||||
function _M.init_worker()
|
||||
local err
|
||||
local cfg = {
|
||||
automatic = true,
|
||||
checker = check_consumer,
|
||||
}
|
||||
|
||||
consumers, err = core.config.new("/consumers", cfg)
|
||||
if not consumers then
|
||||
error("failed to create etcd instance for fetching consumers: " .. err)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local function get_anonymous_consumer_from_local_cache(name)
|
||||
local anon_consumer_raw = consumers:get(name)
|
||||
|
||||
if not anon_consumer_raw or not anon_consumer_raw.value or
|
||||
not anon_consumer_raw.value.id or not anon_consumer_raw.modifiedIndex then
|
||||
return nil, nil, "failed to get anonymous consumer " .. name
|
||||
end
|
||||
|
||||
-- make structure of anon_consumer similar to that of consumer_mod.consumers_kv's response
|
||||
local anon_consumer = anon_consumer_raw.value
|
||||
anon_consumer.consumer_name = anon_consumer_raw.value.id
|
||||
anon_consumer.modifiedIndex = anon_consumer_raw.modifiedIndex
|
||||
|
||||
local anon_consumer_conf = {
|
||||
conf_version = anon_consumer_raw.modifiedIndex
|
||||
}
|
||||
|
||||
return anon_consumer, anon_consumer_conf
|
||||
end
|
||||
|
||||
|
||||
function _M.get_anonymous_consumer(name)
|
||||
local anon_consumer, anon_consumer_conf, err
|
||||
anon_consumer, anon_consumer_conf, err = get_anonymous_consumer_from_local_cache(name)
|
||||
|
||||
return anon_consumer, anon_consumer_conf, err
|
||||
end
|
||||
|
||||
|
||||
return _M
|
Reference in New Issue
Block a user