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:
2025-09-04 09:42:47 -05:00
parent f7bae09f22
commit 54cc5f7308
1608 changed files with 388342 additions and 0 deletions

View File

@@ -0,0 +1,343 @@
--
-- 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 ngx = ngx
local yaml = require("lyaml")
local profile = require("apisix.core.profile")
local util = require("apisix.cli.util")
local schema = require("apisix.cli.schema")
local default_conf = require("apisix.cli.config")
local dkjson = require("dkjson")
local pl_path = require("pl.path")
local pairs = pairs
local type = type
local tonumber = tonumber
local getenv = os.getenv
local str_gmatch = string.gmatch
local str_find = string.find
local str_sub = string.sub
local print = print
local _M = {}
local exported_vars
function _M.get_exported_vars()
return exported_vars
end
local function is_empty_yaml_line(line)
return line == '' or str_find(line, '^%s*$') or str_find(line, '^%s*#')
end
local function tab_is_array(t)
local count = 0
for k, v in pairs(t) do
count = count + 1
end
return #t == count
end
local function var_sub(val)
local err
local var_used = false
-- we use '${{var}}' because '$var' and '${var}' are taken
-- by Nginx
local new_val = val:gsub("%$%{%{%s*([%w_]+[%:%=]?.-)%s*%}%}", function(var)
local i, j = var:find("%:%=")
local default
if i and j then
default = var:sub(i + 2, #var)
default = default:gsub('^%s*(.-)%s*$', '%1')
var = var:sub(1, i - 1)
end
local v = getenv(var) or default
if v then
if not exported_vars then
exported_vars = {}
end
exported_vars[var] = v
var_used = true
return v
end
err = "failed to handle configuration: " ..
"can't find environment variable " .. var
return ""
end)
return new_val, var_used, err
end
local function resolve_conf_var(conf)
local new_keys = {}
for key, val in pairs(conf) do
-- avoid re-iterating the table for already iterated key
if new_keys[key] then
goto continue
end
-- substitute environment variables from conf keys
if type(key) == "string" then
local new_key, _, err = var_sub(key)
if err then
return nil, err
end
if new_key ~= key then
new_keys[new_key] = "dummy" -- we only care about checking the key
conf.key = nil
conf[new_key] = val
key = new_key
end
end
if type(val) == "table" then
local ok, err = resolve_conf_var(val)
if not ok then
return nil, err
end
elseif type(val) == "string" then
local new_val, var_used, err = var_sub(val)
if err then
return nil, err
end
if var_used then
if tonumber(new_val) ~= nil then
new_val = tonumber(new_val)
elseif new_val == "true" then
new_val = true
elseif new_val == "false" then
new_val = false
end
end
conf[key] = new_val
end
::continue::
end
return true
end
_M.resolve_conf_var = resolve_conf_var
local function replace_by_reserved_env_vars(conf)
-- TODO: support more reserved environment variables
local v = getenv("APISIX_DEPLOYMENT_ETCD_HOST")
if v and conf["deployment"] and conf["deployment"]["etcd"] then
local val, _, err = dkjson.decode(v)
if err or not val then
print("parse ${APISIX_DEPLOYMENT_ETCD_HOST} failed, error:", err)
return
end
conf["deployment"]["etcd"]["host"] = val
end
end
local function path_is_multi_type(path, type_val)
if str_sub(path, 1, 14) == "nginx_config->" and
(type_val == "number" or type_val == "string") then
return true
end
if path == "apisix->node_listen" and type_val == "number" then
return true
end
if path == "apisix->data_encryption->keyring" then
return true
end
return false
end
local function merge_conf(base, new_tab, ppath)
ppath = ppath or ""
for key, val in pairs(new_tab) do
if type(val) == "table" then
if val == yaml.null then
base[key] = nil
elseif tab_is_array(val) then
base[key] = val
else
if base[key] == nil then
base[key] = {}
end
local ok, err = merge_conf(
base[key],
val,
ppath == "" and key or ppath .. "->" .. key
)
if not ok then
return nil, err
end
end
else
local type_val = type(val)
if base[key] == nil then
base[key] = val
elseif type(base[key]) ~= type_val then
local path = ppath == "" and key or ppath .. "->" .. key
if path_is_multi_type(path, type_val) then
base[key] = val
else
return nil, "failed to merge, path[" .. path .. "] expect: " ..
type(base[key]) .. ", but got: " .. type_val
end
else
base[key] = val
end
end
end
return base
end
function _M.read_yaml_conf(apisix_home)
if apisix_home then
profile.apisix_home = apisix_home .. "/"
end
local local_conf_path = profile:customized_yaml_path()
if not local_conf_path then
local_conf_path = profile:yaml_path("config")
end
local user_conf_yaml, err = util.read_file(local_conf_path)
if not user_conf_yaml then
return nil, err
end
local is_empty_file = true
for line in str_gmatch(user_conf_yaml .. '\n', '(.-)\r?\n') do
if not is_empty_yaml_line(line) then
is_empty_file = false
break
end
end
if not is_empty_file then
local user_conf = yaml.load(user_conf_yaml)
if not user_conf then
return nil, "invalid config.yaml file"
end
local ok, err = resolve_conf_var(user_conf)
if not ok then
return nil, err
end
ok, err = merge_conf(default_conf, user_conf)
if not ok then
return nil, err
end
end
-- fill the default value by the schema
local ok, err = schema.validate(default_conf)
if not ok then
return nil, err
end
if default_conf.deployment then
default_conf.deployment.config_provider = "etcd"
if default_conf.deployment.role == "traditional" then
default_conf.etcd = default_conf.deployment.etcd
if default_conf.deployment.role_traditional.config_provider == "yaml" then
default_conf.deployment.config_provider = "yaml"
end
elseif default_conf.deployment.role == "control_plane" then
default_conf.etcd = default_conf.deployment.etcd
default_conf.apisix.enable_admin = true
elseif default_conf.deployment.role == "data_plane" then
default_conf.etcd = default_conf.deployment.etcd
if default_conf.deployment.role_data_plane.config_provider == "yaml" then
default_conf.deployment.config_provider = "yaml"
elseif default_conf.deployment.role_data_plane.config_provider == "json" then
default_conf.deployment.config_provider = "json"
elseif default_conf.deployment.role_data_plane.config_provider == "xds" then
default_conf.deployment.config_provider = "xds"
end
default_conf.apisix.enable_admin = false
end
end
--- using `not ngx` to check whether the current execution environment is apisix cli module,
--- because it is only necessary to parse and validate `apisix.yaml` in apisix cli.
if default_conf.deployment.config_provider == "yaml" and not ngx then
local apisix_conf_path = profile:yaml_path("apisix")
local apisix_conf_yaml, _ = util.read_file(apisix_conf_path)
if apisix_conf_yaml then
local apisix_conf = yaml.load(apisix_conf_yaml)
if apisix_conf then
local ok, err = resolve_conf_var(apisix_conf)
if not ok then
return nil, err
end
end
end
end
local apisix_ssl = default_conf.apisix.ssl
if apisix_ssl and apisix_ssl.ssl_trusted_certificate then
-- default value is set to "system" during schema validation
if apisix_ssl.ssl_trusted_certificate == "system" then
local trusted_certs_path, err = util.get_system_trusted_certs_filepath()
if not trusted_certs_path then
util.die(err)
end
apisix_ssl.ssl_trusted_certificate = trusted_certs_path
else
-- During validation, the path is relative to PWD
-- When Nginx starts, the path is relative to conf
-- Therefore we need to check the absolute version instead
local cert_path = pl_path.abspath(apisix_ssl.ssl_trusted_certificate)
if not pl_path.exists(cert_path) then
util.die("certificate path", cert_path, "doesn't exist\n")
end
apisix_ssl.ssl_trusted_certificate = cert_path
end
end
replace_by_reserved_env_vars(default_conf)
return default_conf
end
return _M