diff --git a/bin/apisix b/bin/apisix index 9573f3b22c18b..61f2bb0c6b514 100755 --- a/bin/apisix +++ b/bin/apisix @@ -21,9 +21,9 @@ local function trim(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end --- Note: The `excute_cmd` return value will have a line break at the end, +-- Note: The `execute_cmd` return value will have a line break at the end, -- it is recommended to use the `trim` function to handle the return value. -local function excute_cmd(cmd) +local function execute_cmd(cmd) local t, err = io.popen(cmd) if not t then return nil, "failed to execute command: " .. cmd .. ", error info:" .. err @@ -33,7 +33,7 @@ local function excute_cmd(cmd) return data end -excute_cmd("install -d -m 777 /tmp/apisix_cores/") +execute_cmd("install -d -m 777 /tmp/apisix_cores/") local pkg_cpath_org = package.cpath local pkg_path_org = package.path @@ -42,12 +42,14 @@ local apisix_home = "/usr/local/apisix" local pkg_cpath = apisix_home .. "/deps/lib64/lua/5.1/?.so;" .. apisix_home .. "/deps/lib/lua/5.1/?.so;;" local pkg_path = apisix_home .. "/deps/share/lua/5.1/?.lua;;" +local min_etcd_version = "3.4.0" + -- only for developer, use current folder as working space local is_root_path = false local script_path = arg[0] if script_path:sub(1, 2) == './' then - apisix_home = trim(excute_cmd("pwd")) + apisix_home = trim(execute_cmd("pwd")) if not apisix_home then error("failed to fetch current path") end @@ -640,16 +642,17 @@ local function read_yaml_conf() return default_conf end + local function get_openresty_version() local str = "nginx version: openresty/" - local ret = excute_cmd("openresty -v 2>&1") + local ret = execute_cmd("openresty -v 2>&1") local pos = string.find(ret,str) if pos then return string.sub(ret, pos + string.len(str)) end str = "nginx version: nginx/" - ret = excute_cmd("openresty -v 2>&1") + ret = execute_cmd("openresty -v 2>&1") pos = string.find(ret, str) if pos then return string.sub(ret, pos + string.len(str)) @@ -658,23 +661,82 @@ local function get_openresty_version() return nil end + local function is_32bit_arch() local ok, ffi = pcall(require, "ffi") if ok then -- LuaJIT return ffi.abi("32bit") end - local ret = excute_cmd("getconf LONG_BIT") + local ret = execute_cmd("getconf LONG_BIT") local bits = tonumber(ret) return bits <= 32 end + local function split(self, sep) local sep, fields = sep or ":", {} local pattern = string.format("([^%s]+)", sep) self:gsub(pattern, function(c) fields[#fields + 1] = c end) return fields - end +end + + +local function parse_semantic_version(ver) + local errmsg = "invalid semantic version: " .. ver + + local parts = split(ver, "-") + if #parts > 2 then + return nil, errmsg + end + + if #parts == 2 then + ver = parts[1] + end + + local fields = split(ver, ".") + if #fields ~= 3 then + return nil, errmsg + end + + local major = tonumber(fields[1]) + local minor = tonumber(fields[2]) + local patch = tonumber(fields[3]) + + if not (major and minor and patch) then + return nil, errmsg + end + + return { + major = major, + minor = minor, + patch = patch, + } +end + + +local function compare_semantic_version(v1, v2) + local ver1, err = parse_semantic_version(v1) + if not ver1 then + return nil, err + end + + local ver2, err = parse_semantic_version(v2) + if not ver2 then + return nil, err + end + + if ver1.major ~= ver2.major then + return ver1.major < ver2.major + end + + if ver1.minor ~= ver2.minor then + return ver1.minor < ver2.minor + end + + return ver1.patch < ver2.patch +end + local function check_or_version(cur_ver_s, need_ver_s) local cur_vers = split(cur_ver_s, [[.]]) @@ -744,7 +806,7 @@ local function init() end -- print("etcd: ", yaml_conf.etcd.host) - local or_ver = excute_cmd("openresty -V 2>&1") + local or_ver = execute_cmd("openresty -V 2>&1") local with_module_status = true if or_ver and not or_ver:find("http_stub_status_module", 1, true) then io.stderr:write("'http_stub_status_module' module is missing in ", @@ -766,7 +828,7 @@ local function init() local sys_conf = { lua_path = pkg_path_org, lua_cpath = pkg_cpath_org, - os_name = trim(excute_cmd("uname")), + os_name = trim(execute_cmd("uname")), apisix_lua_home = apisix_home, with_module_status = with_module_status, error_log = {level = "warn"}, @@ -871,13 +933,47 @@ local function init_etcd(show_output) yaml_conf.etcd.host = {yaml_conf.etcd.host} end + local cluster_version local host_count = #(yaml_conf.etcd.host) + -- check the etcd cluster version + for index, host in ipairs(yaml_conf.etcd.host) do + uri = host .. "/version" + local cmd = string.format("curl -s -m %d %s | sed -e 's/[{}]/''/g'", timeout * 2, uri) + local res = execute_cmd(cmd) + local m = split(res, [[%s*,%s*]]) + local errmsg = string.format("got malformed version message: \"%s\" from etcd", res) + + for i = 1, #m do + local mm = split(m[i], [[%s*:%s*]]) + if #mm ~= 2 then + io.stderr:write(errmsg) + return + end + + if mm[1] == "\"etcdcluster\"" then + cluster_version = string.sub(mm[2], 2, -2) + break + end + end + + if not cluster_version then + io.stderr:write(errmsg) + return + end + + if compare_semantic_version(cluster_version, min_etcd_version) then + io.stderr:write(string.format("etcd cluster version \"%s\" is less than the required version \"%s\", please upgrade your etcd cluster", cluster_version, min_etcd_version)) + return + end + + break + end -- check whether the user has enabled etcd v2 protocol for index, host in ipairs(yaml_conf.etcd.host) do uri = host .. "/v2/keys" local cmd = "curl -i -m ".. timeout * 2 .. " -o /dev/null -s -w %{http_code} " .. uri - local res = excute_cmd(cmd) + local res = execute_cmd(cmd) if res == "404" then io.stderr:write(string.format("failed: please make sure that you have enabled the v2 protocol of etcd on %s.\n", host)) return @@ -899,7 +995,7 @@ local function init_etcd(show_output) .. "--connect-timeout " .. timeout .. " --max-time " .. timeout * 2 .. " --retry 1 2>&1" - local res = excute_cmd(cmd) + local res = execute_cmd(cmd) if not res:find("index", 1, true) and not res:find("createdIndex", 1, true) then is_success = false