You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2020/10/16 07:53:40 UTC

[GitHub] [apisix] spacewander commented on a change in pull request #2395: improve: refactor apisix command line tool

spacewander commented on a change in pull request #2395:
URL: https://github.com/apache/apisix/pull/2395#discussion_r506123444



##########
File path: apisix/cmd/env.lua
##########
@@ -0,0 +1,77 @@
+--
+-- 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 util = require "apisix.cmd.util"
+
+local execute_cmd   = util.execute_cmd
+local trim          = util.trim
+local pcall         = pcall
+local error         = error
+local str_match     = string.match
+local pkg_cpath_org = package.cpath
+local pkg_path_org  = package.path
+local apisix_home   = "/usr/local/apisix"

Review comment:
       Style: constant definition should not be here.

##########
File path: apisix/cmd/ops.lua
##########
@@ -0,0 +1,294 @@
+--
+-- 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 env = require "apisix.cmd.env"
+local etcd = require "apisix.cmd.etcd"
+local file = require "apisix.cmd.file"
+local util = require "apisix.cmd.util"
+local ngx_tpl = require "apisix.cmd.ngx_tpl"
+local template = require "resty.template"
+
+local type = type
+local pairs = pairs
+local print = print
+local ipairs = ipairs
+local tonumber = tonumber
+local tostring = tostring
+local str_find = string.find
+local str_sub = string.sub
+local max = math.max
+local popen = io.popen
+local execute = os.execute
+local error = error
+local stderr = io.stderr
+local openresty_args = [[openresty  -p ]] .. env.apisix_home .. [[ -c ]]

Review comment:
       Style: constant definition should be put a few lines after here.

##########
File path: apisix/cmd/file.lua
##########
@@ -0,0 +1,130 @@
+--
+-- 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 yaml = require "tinyyaml"
+local env = require "apisix.cmd.env"
+local profile = require "apisix.core.profile"
+
+local pairs = pairs
+local type = type
+local open = io.open
+local str_find = string.find
+local str_gmatch = string.gmatch
+local _M = {}

Review comment:
       Need blank lines before `_M`.

##########
File path: apisix/cmd/ops.lua
##########
@@ -0,0 +1,294 @@
+--
+-- 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 env = require "apisix.cmd.env"
+local etcd = require "apisix.cmd.etcd"
+local file = require "apisix.cmd.file"
+local util = require "apisix.cmd.util"
+local ngx_tpl = require "apisix.cmd.ngx_tpl"
+local template = require "resty.template"
+
+local type = type
+local pairs = pairs
+local print = print
+local ipairs = ipairs
+local tonumber = tonumber
+local tostring = tostring
+local str_find = string.find
+local str_sub = string.sub
+local max = math.max
+local popen = io.popen
+local execute = os.execute
+local error = error
+local stderr = io.stderr
+local openresty_args = [[openresty  -p ]] .. env.apisix_home .. [[ -c ]]
+                       .. env.apisix_home .. [[/conf/nginx.conf]]
+
+local _M = {}
+
+
+local function get_openresty_version()
+    local str = "nginx version: openresty/"
+    local ret = util.execute_cmd("openresty -v 2>&1")
+    local pos = str_find(ret, str)
+    if pos then
+        return str_sub(ret, pos + #str)
+    end
+
+    str = "nginx version: nginx/"
+    ret = util.execute_cmd("openresty -v 2>&1")
+    pos = str_find(ret, str)
+    if pos then
+        return str_sub(ret, pos + #str)
+    end
+
+    return nil
+end
+
+
+local function check_version(cur_ver_s, need_ver_s)
+    local cur_vers = util.split(cur_ver_s, [[.]])
+    local need_vers = util.split(need_ver_s, [[.]])
+    local len = max(#cur_vers, #need_vers)
+
+    for i = 1, len do
+        local cur_ver = tonumber(cur_vers[i]) or 0
+        local need_ver = tonumber(need_vers[i]) or 0
+        if cur_ver > need_ver then
+            return true
+        end
+
+        if cur_ver < need_ver then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+function _M.init()
+    if env.is_root_path then
+        print("Warning! Running apisix under /root is only suitable for "
+              .. "development environments and it is dangerous to do so."
+              .. "It is recommended to run APISIX in a directory other than "
+              .. "/root.")
+    end
+
+    -- read_yaml_conf
+    local yaml_conf, err = file.read_yaml_conf()
+    if not yaml_conf then
+        error("failed to read local yaml config of apisix: " .. err)
+    end
+
+    -- check the Admin API token
+    local checked_admin_key = false
+    if yaml_conf.apisix.enable_admin and yaml_conf.apisix.allow_admin then
+        for _, allow_ip in ipairs(yaml_conf.apisix.allow_admin) do
+            if allow_ip == "127.0.0.0/24" then
+                checked_admin_key = true
+                break
+            end
+        end
+    end
+
+    if yaml_conf.apisix.enable_admin and not checked_admin_key then
+        local help = [[
+
+%s
+Please modify "admin_key" in conf/config.yaml .
+
+]]
+        if type(yaml_conf.apisix.admin_key) ~= "table" or
+           #yaml_conf.apisix.admin_key == 0
+        then
+            return util.die(help:format("ERROR: missing valid Admin API token."))
+        end
+
+        for _, admin in ipairs(yaml_conf.apisix.admin_key) do
+            if type(admin.key) == "table" then
+                admin.key = ""
+            else
+                admin.key = tostring(admin.key)
+            end
+
+            if admin.key == "" then
+                return util.die(help:format("ERROR: missing valid Admin API token."),
+                                "\n")
+            end
+
+            if admin.key == "edd1c9f034335f136f87ad84b625c8f1" then
+                local msg = help:format([[WARNING: using fixed Admin API token has security risk.]])

Review comment:
       Should use `ERROR` here since we will exit.

##########
File path: apisix/cmd/ops.lua
##########
@@ -0,0 +1,294 @@
+--
+-- 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 env = require "apisix.cmd.env"
+local etcd = require "apisix.cmd.etcd"
+local file = require "apisix.cmd.file"
+local util = require "apisix.cmd.util"
+local ngx_tpl = require "apisix.cmd.ngx_tpl"
+local template = require "resty.template"
+
+local type = type
+local pairs = pairs
+local print = print
+local ipairs = ipairs
+local tonumber = tonumber
+local tostring = tostring
+local str_find = string.find
+local str_sub = string.sub
+local max = math.max
+local popen = io.popen
+local execute = os.execute
+local error = error
+local stderr = io.stderr
+local openresty_args = [[openresty  -p ]] .. env.apisix_home .. [[ -c ]]
+                       .. env.apisix_home .. [[/conf/nginx.conf]]
+
+local _M = {}
+
+
+local function get_openresty_version()
+    local str = "nginx version: openresty/"
+    local ret = util.execute_cmd("openresty -v 2>&1")
+    local pos = str_find(ret, str)

Review comment:
       Plain string find is required.

##########
File path: apisix/cmd/ops.lua
##########
@@ -0,0 +1,294 @@
+--
+-- 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 env = require "apisix.cmd.env"
+local etcd = require "apisix.cmd.etcd"
+local file = require "apisix.cmd.file"
+local util = require "apisix.cmd.util"
+local ngx_tpl = require "apisix.cmd.ngx_tpl"
+local template = require "resty.template"
+
+local type = type
+local pairs = pairs
+local print = print
+local ipairs = ipairs
+local tonumber = tonumber
+local tostring = tostring
+local str_find = string.find
+local str_sub = string.sub
+local max = math.max
+local popen = io.popen
+local execute = os.execute
+local error = error
+local stderr = io.stderr
+local openresty_args = [[openresty  -p ]] .. env.apisix_home .. [[ -c ]]
+                       .. env.apisix_home .. [[/conf/nginx.conf]]
+
+local _M = {}
+
+
+local function get_openresty_version()
+    local str = "nginx version: openresty/"
+    local ret = util.execute_cmd("openresty -v 2>&1")
+    local pos = str_find(ret, str)
+    if pos then
+        return str_sub(ret, pos + #str)
+    end
+
+    str = "nginx version: nginx/"
+    ret = util.execute_cmd("openresty -v 2>&1")
+    pos = str_find(ret, str)
+    if pos then
+        return str_sub(ret, pos + #str)
+    end
+
+    return nil
+end
+
+
+local function check_version(cur_ver_s, need_ver_s)
+    local cur_vers = util.split(cur_ver_s, [[.]])
+    local need_vers = util.split(need_ver_s, [[.]])
+    local len = max(#cur_vers, #need_vers)
+
+    for i = 1, len do
+        local cur_ver = tonumber(cur_vers[i]) or 0
+        local need_ver = tonumber(need_vers[i]) or 0
+        if cur_ver > need_ver then
+            return true
+        end
+
+        if cur_ver < need_ver then
+            return false
+        end
+    end
+
+    return true
+end
+
+
+function _M.init()
+    if env.is_root_path then
+        print("Warning! Running apisix under /root is only suitable for "
+              .. "development environments and it is dangerous to do so."
+              .. "It is recommended to run APISIX in a directory other than "
+              .. "/root.")
+    end
+
+    -- read_yaml_conf
+    local yaml_conf, err = file.read_yaml_conf()
+    if not yaml_conf then
+        error("failed to read local yaml config of apisix: " .. err)
+    end
+
+    -- check the Admin API token
+    local checked_admin_key = false
+    if yaml_conf.apisix.enable_admin and yaml_conf.apisix.allow_admin then
+        for _, allow_ip in ipairs(yaml_conf.apisix.allow_admin) do
+            if allow_ip == "127.0.0.0/24" then
+                checked_admin_key = true
+                break
+            end
+        end
+    end
+
+    if yaml_conf.apisix.enable_admin and not checked_admin_key then
+        local help = [[
+
+%s
+Please modify "admin_key" in conf/config.yaml .
+
+]]
+        if type(yaml_conf.apisix.admin_key) ~= "table" or
+           #yaml_conf.apisix.admin_key == 0
+        then
+            return util.die(help:format("ERROR: missing valid Admin API token."))
+        end
+
+        for _, admin in ipairs(yaml_conf.apisix.admin_key) do
+            if type(admin.key) == "table" then
+                admin.key = ""
+            else
+                admin.key = tostring(admin.key)
+            end
+
+            if admin.key == "" then
+                return util.die(help:format("ERROR: missing valid Admin API token."),
+                                "\n")
+            end
+
+            if admin.key == "edd1c9f034335f136f87ad84b625c8f1" then
+                local msg = help:format([[WARNING: using fixed Admin API token has security risk.]])
+                return util.die(msg, "\n")
+            end
+        end
+    end
+
+    local with_module_status = true
+
+    local or_ver = util.execute_cmd("openresty -V 2>&1")
+    if or_ver and not or_ver:find("http_stub_status_module", 1, true) then
+        stderr:write("'http_stub_status_module' module is missing in ",
+                     "your openresty, please check it out. Without this ",
+                     "module, there will be fewer monitoring indicators.\n")
+
+        with_module_status = false
+    end
+
+    local enabled_plugins = {}
+    for i, name in ipairs(yaml_conf.plugins) do
+        enabled_plugins[name] = true
+    end
+
+    if enabled_plugins["proxy-cache"] and not yaml_conf.apisix.proxy_cache then
+        error("missing apisix.proxy_cache for plugin proxy-cache")
+    end
+
+    -- Using template.render
+    local sys_conf = {
+        lua_path           = env.pkg_path_org,
+        lua_cpath          = env.pkg_cpath_org,
+        apisix_lua_home    = env.apisix_home,
+        os_name            = util.trim(util.execute_cmd("uname")),
+        with_module_status = with_module_status,
+        error_log          = { level = "warn" },
+        enabled_plugins    = enabled_plugins,
+    }
+
+    if not yaml_conf.apisix then
+        error("failed to read `apisix` field from yaml file")
+    end
+
+    if not yaml_conf.nginx_config then
+        error("failed to read `nginx_config` field from yaml file")
+    end
+
+    if util.is_32bit_arch() then
+        sys_conf["worker_rlimit_core"] = "4G"
+    else
+        sys_conf["worker_rlimit_core"] = "16G"
+    end
+
+    for k,v in pairs(yaml_conf.apisix) do
+        sys_conf[k] = v
+    end
+
+    for k,v in pairs(yaml_conf.nginx_config) do
+        sys_conf[k] = v
+    end
+
+    local wrn = sys_conf["worker_rlimit_nofile"]
+    local wc = sys_conf["event"]["worker_connections"]
+    if not wrn or wrn <= wc then
+        -- ensure the number of fds is slightly larger than the number of conn
+        sys_conf["worker_rlimit_nofile"] = wc + 128
+    end
+
+    if sys_conf["enable_dev_mode"] == true then
+        sys_conf["worker_processes"] = 1
+        sys_conf["enable_reuseport"] = false
+
+    elseif tonumber(sys_conf["worker_processes"]) == nil then
+        sys_conf["worker_processes"] = "auto"
+    end
+
+    local dns_resolver = sys_conf["dns_resolver"]
+    if not dns_resolver or #dns_resolver == 0 then
+        local dns_addrs, err = util.local_dns_resolver("/etc/resolv.conf")
+        if not dns_addrs then
+            return util.die("failed to import local DNS: ", err)
+        end
+
+        if #dns_addrs == 0 then
+            return util.die("local DNS is empty")
+        end
+
+        sys_conf["dns_resolver"] = dns_addrs
+    end
+
+    local conf_render = template.compile(ngx_tpl)
+    local ngxconf = conf_render(sys_conf)
+
+    local ok, err = file.write_file(env.apisix_home .. "/conf/nginx.conf",
+                                    ngxconf)
+    if not ok then
+        return util.die("failed to update nginx.conf: ", err)
+    end
+
+    local op_ver = get_openresty_version()
+    if op_ver == nil then
+        return util.die("can not find openresty\n")
+    end
+
+    local need_ver = "1.15.8"
+    if not check_version(op_ver, need_ver) then
+        return util.die("openresty version must >=", need_ver, " current ", op_ver, "\n")
+    end
+end
+
+
+function _M.start(...)
+    -- check running
+    local pid = file.read_file(env.pid_path)
+    if pid then
+        local hd = popen("lsof -p " .. pid)
+        local res = hd:read("*a")
+        if res and res ~= "" then
+            print("APISIX is running...")
+            return nil
+        end
+    end
+
+    _M.init(...)
+    etcd.init(...)
+
+    execute(openresty_args)
+end
+
+
+function _M.stop()
+    execute(openresty_args .. [[ -s stop]])
+end
+
+
+function _M.restart()
+    _M.stop()
+    _M.start()
+end
+
+
+function _M.reload()
+    -- reinit nginx.conf
+    _M.init()
+
+    local test_cmd = openresty_args .. [[ -t -q ]]
+    -- When success,
+    -- On linux, os.execute returns 0,
+    -- On macos, os.execute returns 3 values: true, exit, 0,
+    -- and we need the first.
+    local test_ret = execute(test_cmd)
+    if test_ret == 0 or test_ret == true then
+        local cmd = openresty_args .. [[ -s reload]]
+        execute(cmd)
+        return
+    end
+
+    util.die("test openresty failed")
+end
+
+
+return _M

Review comment:
       Missing `_M.help` and `_M.version`?

##########
File path: apisix/cmd/etcd.lua
##########
@@ -0,0 +1,196 @@
+--
+-- 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 dkjson = require "dkjson"
+local file = require "apisix.cmd.file"
+local util = require "apisix.cmd.util"
+local env = require "apisix.cmd.env"
+
+local base64_encode = require("base64").encode
+
+local type = type
+local ipairs = ipairs
+local print = print
+local tonumber = tonumber
+local str_format = string.format
+
+local _M = {}
+
+
+local function parse_semantic_version(ver)
+    local errmsg = "invalid semantic version: " .. ver
+
+    local parts = util.split(ver, "-")
+    if #parts > 2 then
+        return nil, errmsg
+    end
+
+    if #parts == 2 then
+        ver = parts[1]
+    end
+
+    local fields = util.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
+
+
+function _M.init(show_output)
+    -- read_yaml_conf
+    local yaml_conf, err = file.read_yaml_conf()
+    if not yaml_conf then
+        return util.die("failed to read local yaml config of apisix: ",
+                        err)
+    end
+
+    if not yaml_conf.apisix then
+        return util.die("failed to read `apisix` field from yaml file ",
+                        "while initializing etcd")
+    end
+
+    if yaml_conf.apisix.config_center ~= "etcd" then
+        return true
+    end
+
+    if not yaml_conf.etcd then
+        return util.die("failed to read `etcd` field from yaml file ",
+                        "while initializing etcd")
+    end
+
+    --convert old single etcd config to multiple etcd config
+    if type(yaml_conf.etcd.host) == "string" then
+        yaml_conf.etcd.host = { yaml_conf.etcd.host }
+    end
+
+    local etcd_conf = yaml_conf.etcd
+    local timeout = yaml_conf.etcd.timeout or 3
+    local host_count = #(yaml_conf.etcd.host)
+    local uri
+
+    for index, host in ipairs(yaml_conf.etcd.host) do
+        -- check the etcd cluster version
+        uri = etcd_conf.host[1] .. "/version"

Review comment:
       The target host should be different in the loop?

##########
File path: apisix/cmd/file.lua
##########
@@ -0,0 +1,130 @@
+--
+-- 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 yaml = require "tinyyaml"
+local env = require "apisix.cmd.env"
+local profile = require "apisix.core.profile"
+
+local pairs = pairs
+local type = type
+local open = io.open
+local str_find = string.find
+local str_gmatch = string.gmatch
+local _M = {}
+
+
+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 merge_conf
+merge_conf = function(base, new_tab)
+    for key, val in pairs(new_tab) do
+        if type(val) == "table" then
+            if tab_is_array(val) then
+                base[key] = val
+            else
+                merge_conf(base[key], val)
+            end
+
+        else
+            base[key] = val
+        end
+    end
+
+    return base
+end
+
+
+local function is_empty_yaml_line(line)
+    return line == '' or str_find(line, '^%s*$') or str_find(line, '^%s*#')
+end
+
+
+function _M.write_file(file_path, data)
+    local file, err = open(file_path, "w+")
+    if not file then
+        return false, "failed to open file: " .. file_path .. ", error info:" .. err
+    end
+
+    file:write(data)

Review comment:
       Should check the returned value of `write`.

##########
File path: apisix/cmd/file.lua
##########
@@ -0,0 +1,130 @@
+--
+-- 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 yaml = require "tinyyaml"
+local env = require "apisix.cmd.env"
+local profile = require "apisix.core.profile"
+
+local pairs = pairs
+local type = type
+local open = io.open
+local str_find = string.find
+local str_gmatch = string.gmatch
+local _M = {}
+
+
+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 merge_conf
+merge_conf = function(base, new_tab)
+    for key, val in pairs(new_tab) do
+        if type(val) == "table" then
+            if tab_is_array(val) then
+                base[key] = val
+            else
+                merge_conf(base[key], val)
+            end
+
+        else
+            base[key] = val
+        end
+    end
+
+    return base
+end
+
+
+local function is_empty_yaml_line(line)
+    return line == '' or str_find(line, '^%s*$') or str_find(line, '^%s*#')
+end
+
+
+function _M.write_file(file_path, data)
+    local file, err = open(file_path, "w+")
+    if not file then
+        return false, "failed to open file: " .. file_path .. ", error info:" .. err
+    end
+
+    file:write(data)
+    file:close()
+
+    return true
+end
+
+
+function _M.read_file(file_path)
+    local file, err = open(file_path, "rb")
+    if not file then
+        return false, "failed to open file: " .. file_path .. ", error info:" .. err
+    end
+
+    local data = file:read("*all")

Review comment:
       Should check the returned value of `read`.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org