This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new f5f402f44 feat: supports dynamic control of whether plugins are 
running (#7453)
f5f402f44 is described below

commit f5f402f4484dc09300860a1798e003410e1fb456
Author: soulbird <[email protected]>
AuthorDate: Wed Jul 20 14:45:12 2022 +0800

    feat: supports dynamic control of whether plugins are running (#7453)
---
 apisix/plugin.lua                    |  48 +++++++-
 apisix/schema_def.lua                |   9 ++
 docs/en/latest/terminology/plugin.md |  15 +++
 docs/zh/latest/terminology/plugin.md |  15 +++
 t/admin/plugins.t                    |   4 +-
 t/plugin/plugin.t                    | 221 ++++++++++++++++++++++++++++++++++-
 6 files changed, 306 insertions(+), 6 deletions(-)

diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index b20ff2787..e87dbc750 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -19,6 +19,7 @@ local core          = require("apisix.core")
 local config_util   = require("apisix.core.config_util")
 local enable_debug  = require("apisix.debug").enable_debug
 local wasm          = require("apisix.wasm")
+local expr          = require("resty.expr.v1")
 local ngx           = ngx
 local crc32         = ngx.crc32_short
 local ngx_exit      = ngx.exit
@@ -40,6 +41,9 @@ local stream_local_plugins_hash = core.table.new(0, 32)
 local merged_route = core.lrucache.new({
     ttl = 300, count = 512
 })
+local expr_lrucache = core.lrucache.new({
+    ttl = 300, count = 512
+})
 local local_conf
 local check_plugin_metadata
 
@@ -381,6 +385,32 @@ local function trace_plugins_info_for_debug(ctx, plugins)
     end
 end
 
+local function meta_filter(ctx, plugin_name, plugin_conf)
+    local filter = plugin_conf._meta and plugin_conf._meta.filter
+    if not filter then
+        return true
+    end
+
+    local ex, ok, err
+    if ctx then
+        ex, err = expr_lrucache(plugin_name .. ctx.conf_type .. ctx.conf_id,
+                                 ctx.conf_version, expr.new, filter)
+    else
+        ex, err = expr.new(filter)
+    end
+    if not ex then
+        core.log.warn("failed to get the 'vars' expression: ", err ,
+                         " plugin_name: ", plugin_name)
+        return true
+    end
+    ok, err = ex:eval()
+    if err then
+        core.log.warn("failed to run the 'vars' expression: ", err,
+                         " plugin_name: ", plugin_name)
+        return true
+    end
+    return ok
+end
 
 function _M.filter(ctx, conf, plugins, route_conf, phase)
     local user_plugin_conf = conf.value.plugins
@@ -399,7 +429,12 @@ function _M.filter(ctx, conf, plugins, route_conf, phase)
         local name = plugin_obj.name
         local plugin_conf = user_plugin_conf[name]
 
-        if type(plugin_conf) == "table" and not plugin_conf.disable then
+        if type(plugin_conf) ~= "table" then
+            goto continue
+        end
+
+        local matched = meta_filter(ctx, name, plugin_conf)
+        if not plugin_conf.disable and matched then
             if plugin_obj.run_policy == "prefer_route" and route_plugin_conf 
~= nil then
                 local plugin_conf_in_route = route_plugin_conf[name]
                 if plugin_conf_in_route and not plugin_conf_in_route.disable 
then
@@ -412,9 +447,9 @@ function _M.filter(ctx, conf, plugins, route_conf, phase)
             end
             core.table.insert(plugins, plugin_obj)
             core.table.insert(plugins, plugin_conf)
-
-            ::continue::
         end
+
+        ::continue::
     end
 
     trace_plugins_info_for_debug(ctx, plugins)
@@ -735,6 +770,13 @@ local function check_single_plugin_schema(name, 
plugin_conf, schema_type, skip_d
                 .. name .. " err: " .. err
         end
 
+        if plugin_conf._meta and plugin_conf._meta.filter then
+            ok, err = expr.new(plugin_conf._meta.filter)
+            if not ok then
+                return nil, "failed to validate the 'vars' expression: " .. err
+            end
+        end
+
         plugin_conf.disable = disable
     end
 
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 16dccc6d8..232be9d86 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -956,6 +956,15 @@ _M.plugin_injected_schema = {
                 description = "priority of plugins by customized order",
                 type = "integer",
             },
+            filter = {
+                description = "filter determines whether the plugin "..
+                                "needs to be executed at runtime",
+                type  = "array",
+                maxItems = 20,
+                items = {
+                    type = "array"
+                }
+            }
         }
     }
 }
diff --git a/docs/en/latest/terminology/plugin.md 
b/docs/en/latest/terminology/plugin.md
index 6ab969769..f5c81c2b4 100644
--- a/docs/en/latest/terminology/plugin.md
+++ b/docs/en/latest/terminology/plugin.md
@@ -90,12 +90,27 @@ Some common configurations can be applied to the plugin 
configuration. For examp
 
 the configuration above means customizing the error response from the jwt-auth 
plugin to '{"message": "Missing credential in request"}'.
 
+```
+{
+    "jwt-auth": {
+        "_meta": {
+            "filter": {
+                {"arg_version", "==", "v2"}
+            }
+        }
+    }
+}
+```
+
+This configuration example means that the `jwt-auth` plugin will only execute 
if `version` in the request parameter equals `v2`.
+
 ### Plugin Common Configuration Under `_meta`
 
 | Name         | Type | Description |
 |--------------|------|-------------|
 | error_response | string/object  | Custom error response |
 | priority       | integer        | Custom plugin priority |
+| filter  | array | Depending on the requested parameters, it is decided at 
runtime whether the plugin should be executed. List of variables to match for 
filtering requests for conditional traffic split. It is in the format {variable 
operator value}. For example, `{"arg_name", "==", "json"}`. The variables here 
are consistent with Nginx internal variables. For details on supported 
operators, please see 
[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). |
 
 ### Custom Plugin Priority
 
diff --git a/docs/zh/latest/terminology/plugin.md 
b/docs/zh/latest/terminology/plugin.md
index 86bed6442..d6bea2e18 100644
--- a/docs/zh/latest/terminology/plugin.md
+++ b/docs/zh/latest/terminology/plugin.md
@@ -84,12 +84,27 @@ local _M = {
 
 上面的配置意味着将 jwt-auth 插件的错误响应自定义为 '{"message": "Missing credential in request"}'。
 
+```
+{
+    "jwt-auth": {
+        "_meta": {
+            "filter": {
+                {"arg_version", "==", "v2"}
+            }
+        }
+    }
+}
+```
+
+这个配置示例意味着只有在请求参数中 `version` 等于 `v2` 时 `jwt-auth` 插件才会执行。
+
 ### 在 `_meta` 下的插件通用配置
 
 | 名称         | 类型 | 描述           |
 |--------------|------|----------------|
 | error_response | string/object  | 自定义错误响应 |
 | priority       | integer        | 自定义插件优先级 |
+| filter  | array   | 根据请求的参数,在运行时控制插件是否执行。由一个或多个{var, operator, 
val}元素组成列表,类似这样:{{var, operator, val}, {var, operator, val}, 
...}}。例如:`{"arg_name", "==", "json"}`,表示当前请求参数 name 是 json。这里的 var 与 Nginx 
内部自身变量命名是保持一致。操作符的具体用法请看[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list)
 的 operator-list 部分。|
 
 ### 自定义插件优先级
 
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index d7881249d..e7a04bd97 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -265,7 +265,7 @@ plugins:
         }
     }
 --- response_body eval
-qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"skey":\{"type":"string"\}\},"required":\["ikey","skey"\],"type":"object"\},"priority":0,"schema":\{"\$comment":"this
 is a mark for our injected plugin 
schema","properties":\{"_meta":\{"properties":\{"error_response":\{"oneOf":\[\{"type":"string"\},\{"type":"object"\}\]\},"priority":\{"description":"priority
 of plugins by customized 
order","type":"integer"\}\},"type":"object"\},"disable":\{"type":"boolean"\}," 
[...]
+qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"skey":\{"type":"string"\}\},"required":\["ikey","skey"\],"type":"object"\},"priority":0,"schema":\{"\$comment":"this
 is a mark for our injected plugin 
schema","properties":\{"_meta":\{"properties":\{"error_response":\{"oneOf":\[\{"type":"string"\},\{"type":"object"\}\]\},"filter":\{"description":"filter
 determines whether the plugin needs to be executed at 
runtime","items":\{"type":"array"\},"maxItems":20,"ty [...]
 
 
 
@@ -366,7 +366,7 @@ 
qr/\{"properties":\{"password":\{"type":"string"\},"username":\{"type":"string"\
         }
     }
 --- response_body
-{"priority":1003,"schema":{"$comment":"this is a mark for our injected plugin 
schema","properties":{"_meta":{"properties":{"error_response":{"oneOf":[{"type":"string"},{"type":"object"}]},"priority":{"description":"priority
 of plugins by customized 
order","type":"integer"}},"type":"object"},"burst":{"minimum":0,"type":"integer"},"conn":{"exclusiveMinimum":0,"type":"integer"},"default_conn_delay":{"exclusiveMinimum":0,"type":"number"},"disable":{"type":"boolean"},"key":{"type":"string"},"
 [...]
+{"priority":1003,"schema":{"$comment":"this is a mark for our injected plugin 
schema","properties":{"_meta":{"properties":{"error_response":{"oneOf":[{"type":"string"},{"type":"object"}]},"filter":{"description":"filter
 determines whether the plugin needs to be executed at 
runtime","items":{"type":"array"},"maxItems":20,"type":"array"},"priority":{"description":"priority
 of plugins by customized 
order","type":"integer"}},"type":"object"},"burst":{"minimum":0,"type":"integer"},"conn":{"ex
 [...]
 
 
 
diff --git a/t/plugin/plugin.t b/t/plugin/plugin.t
index e45f5d5f7..755294868 100644
--- a/t/plugin/plugin.t
+++ b/t/plugin/plugin.t
@@ -284,7 +284,7 @@ GET /hello
                     error_response = "OK"
                 }},
             }) do
-                local code, body = t('/apisix/admin/global_rules/1',
+                local code, body = t('/apisix/admin/plugin_configs/1',
                     ngx.HTTP_PUT,
                     {
                         plugins = {
@@ -306,3 +306,222 @@ GET /hello
 {"error_msg":"failed to check the configuration of plugin jwt-auth err: 
property \"_meta\" validation failed: wrong type: expected object, got boolean"}
 {"error_msg":"failed to check the configuration of plugin jwt-auth err: 
property \"_meta\" validation failed: property \"error_response\" validation 
failed: value should match only one schema, but matches none"}
 passed
+
+
+
+=== TEST 10: invalid _meta filter vars schema with wrong type
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/plugin_configs/1',
+                ngx.HTTP_PUT,
+                {
+                    plugins = {
+                        ["jwt-auth"] = {
+                            _meta = {
+                                filter = "arg_k == v"
+                            }
+                        }
+                    }
+                }
+            )
+            if code >= 300 then
+                ngx.print(body)
+            else
+                ngx.say(body)
+            end
+        }
+    }
+--- response_body
+{"error_msg":"failed to check the configuration of plugin jwt-auth err: 
property \"_meta\" validation failed: property \"filter\" validation failed: 
wrong type: expected array, got string"}
+
+
+
+=== TEST 11: invalid _meta filter schema with wrong expr
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            for _, filter in ipairs({
+                {"arg_name", "==", "json"},
+                {
+                    {"arg_name", "*=", "json"}
+                }
+            }) do
+                local code, body = t('/apisix/admin/plugin_configs/1',
+                    ngx.HTTP_PUT,
+                    {
+                        plugins = {
+                            ["jwt-auth"] = {
+                                _meta = {
+                                    filter = filter
+                                }
+                            }
+                        }
+                    }
+                )
+                if code >= 300 then
+                    ngx.print(body)
+                else
+                    ngx.say(body)
+                end
+            end
+        }
+    }
+--- response_body
+{"error_msg":"failed to check the configuration of plugin jwt-auth err: 
property \"_meta\" validation failed: property \"filter\" validation failed: 
failed to validate item 1: wrong type: expected array, got string"}
+{"error_msg":"failed to validate the 'vars' expression: invalid operator '*='"}
+
+
+
+=== TEST 12: proxy-rewrite plugin run with _meta filter vars
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                {
+                    plugins = {
+                        ["proxy-rewrite"] = {
+                            _meta = {
+                                filter = {
+                                    {"arg_version", "==", "v2"}
+                                }
+                            },
+                            uri = "/echo",
+                            headers = {
+                                ["X-Api-Version"] = "v2"
+                            }
+                        }
+                    },
+                    upstream = {
+                        nodes = {
+                            ["127.0.0.1:1980"] = 1
+                        },
+                        type = "roundrobin"
+                    },
+                    uri = "/hello"
+                }
+            )
+            if code >= 300 then
+                ngx.print(body)
+            else
+                ngx.say(body)
+            end
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 13: hit route: run proxy-rewrite plugin
+--- request
+GET /hello?version=v2
+--- response_headers
+x-api-version: v2
+
+
+
+=== TEST 14: hit route: not run proxy-rewrite plugin
+--- request
+GET /hello?version=v1
+--- response_body
+hello world
+
+
+
+=== TEST 15: different route,same plugin, different filter (for expr_lrucache)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                {
+                    plugins = {
+                        ["proxy-rewrite"] = {
+                            _meta = {
+                                filter = {
+                                    {"arg_version", "==", "v3"}
+                                }
+                            },
+                            uri = "/echo",
+                            headers = {
+                                ["X-Api-Version"] = "v3"
+                            }
+                        }
+                    },
+                    upstream = {
+                        nodes = {
+                            ["127.0.0.1:1980"] = 1
+                        },
+                        type = "roundrobin"
+                    },
+                    uri = "/hello1"
+                }
+            )
+            if code >= 300 then
+                ngx.print(body)
+            else
+                ngx.say(body)
+            end
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 16: hit route: run proxy-rewrite plugin
+--- request
+GET /hello1?version=v3
+--- response_headers
+x-api-version: v3
+
+
+
+=== TEST 17: same plugin, same id between routes and global_rules, different 
filter (for expr_lrucache)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/global_rules/2',
+                ngx.HTTP_PUT,
+                {
+                    plugins = {
+                        ["proxy-rewrite"] = {
+                            _meta = {
+                                filter = {
+                                    {"arg_version", "==", "v4"}
+                                }
+                            },
+                            uri = "/echo",
+                            headers = {
+                                ["X-Api-Version"] = "v4"
+                            }
+                        }
+                    }
+                }
+            )
+            if code >= 300 then
+                ngx.print(body)
+            else
+                ngx.say(body)
+            end
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 18: hit route: run proxy-rewrite plugin
+--- request
+GET /hello1?version=v4
+--- response_headers
+x-api-version: v4

Reply via email to