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:
343
CloudronPackages/APISIX/apisix-source/apisix/cli/file.lua
Normal file
343
CloudronPackages/APISIX/apisix-source/apisix/cli/file.lua
Normal 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
|
Reference in New Issue
Block a user