You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by we...@apache.org on 2025/05/29 02:58:28 UTC

(apisix) branch master updated: feat: fill in the metadata of resource schema (#12224)

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

wenming 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 3cb191ac8 feat: fill in the metadata of resource schema (#12224)
3cb191ac8 is described below

commit 3cb191ac87421594db5bcbe7a09793f15f61d694
Author: Zeping Bai <bz...@apache.org>
AuthorDate: Thu May 29 10:58:24 2025 +0800

    feat: fill in the metadata of resource schema (#12224)
---
 apisix/schema_def.lua       | 104 ++++++++++++++++++---------
 docs/en/latest/admin-api.md |  15 +++-
 docs/zh/latest/admin-api.md |   6 ++
 t/admin/metadata.spec.ts    | 170 ++++++++++++++++++++++++++++++++++++++++++++
 t/admin/metadata.t          |  36 ++++++++++
 t/ts/admin_api.ts           |  13 +++-
 6 files changed, 306 insertions(+), 38 deletions(-)

diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index fd773990d..c543952dd 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -383,8 +383,15 @@ local private_key_schema = {
 local upstream_schema = {
     type = "object",
     properties = {
+        -- metadata
+        id = id_schema,
+        name = rule_name_def,
+        desc = desc_def,
+        labels = labels_def,
         create_time = timestamp_def,
         update_time = timestamp_def,
+
+        -- properties
         nodes = nodes_schema,
         retries = {
             type = "integer",
@@ -466,7 +473,6 @@ local upstream_schema = {
                 " For L4 proxy, it can be one of tcp/tls/udp." ..
                 " For specific protocols, it can be kafka."
         },
-        labels = labels_def,
         discovery_type = {
             description = "discovery type",
             type = "string",
@@ -491,14 +497,11 @@ local upstream_schema = {
             default = "pass"
         },
         upstream_host = host_def,
-        name = rule_name_def,
-        desc = desc_def,
         service_name = {
             type = "string",
             maxLength = 256,
             minLength = 1
         },
-        id = id_schema,
     },
     oneOf = {
         {required = {"nodes"}},
@@ -542,8 +545,15 @@ _M.method_schema = method_schema
 _M.route = {
     type = "object",
     properties = {
+        -- metadata
+        id = id_schema,
+        name = rule_name_def,
+        desc = desc_def,
+        labels = labels_def,
         create_time = timestamp_def,
         update_time = timestamp_def,
+
+        -- properties
         uri = {type = "string", minLength = 1, maxLength = 4096},
         uris = {
             type = "array",
@@ -554,8 +564,6 @@ _M.route = {
             minItems = 1,
             uniqueItems = true,
         },
-        name = rule_name_def,
-        desc = desc_def,
         priority = {type = "integer", default = 0},
 
         methods = {
@@ -596,8 +604,6 @@ _M.route = {
 
         upstream = upstream_schema,
 
-        labels = labels_def,
-
         service_id = id_schema,
         upstream_id = id_schema,
 
@@ -606,8 +612,6 @@ _M.route = {
             type        = "boolean",
         },
 
-        id = id_schema,
-
         status = {
             description = "route status, 1 to enable, 0 to disable",
             type = "integer",
@@ -672,16 +676,19 @@ _M.route = {
 _M.service = {
     type = "object",
     properties = {
+        -- metadata
         id = id_schema,
-        plugins = plugins_schema,
-        upstream = upstream_schema,
-        upstream_id = id_schema,
         name = rule_name_def,
         desc = desc_def,
         labels = labels_def,
-        script = {type = "string", minLength = 10, maxLength = 102400},
         create_time = timestamp_def,
         update_time = timestamp_def,
+
+        -- properties
+        plugins = plugins_schema,
+        upstream = upstream_schema,
+        upstream_id = id_schema,
+        script = {type = "string", minLength = 10, maxLength = 102400},
         enable_websocket = {
             description = "enable websocket for request",
             type        = "boolean",
@@ -700,16 +707,19 @@ _M.service = {
 _M.consumer = {
     type = "object",
     properties = {
+        -- metadata
         username = {
             type = "string", minLength = 1, maxLength = rule_name_def.maxLength,
             pattern = [[^[a-zA-Z0-9_]+$]]
         },
-        group_id = id_schema,
-        plugins = plugins_schema,
+        desc = desc_def,
         labels = labels_def,
         create_time = timestamp_def,
         update_time = timestamp_def,
-        desc = desc_def,
+
+        -- properties
+        group_id = id_schema,
+        plugins = plugins_schema,
     },
     required = {"username"},
     additionalProperties = false,
@@ -718,16 +728,21 @@ _M.consumer = {
 _M.credential = {
     type = "object",
     properties = {
+        -- metadata
         id = id_schema,
+        name = rule_name_def,
+        desc = desc_def,
+        labels = labels_def,
+        create_time = timestamp_def,
+        update_time = timestamp_def,
+
+        -- properties
         plugins = {
             type = "object",
             maxProperties = 1,
         },
-        labels = labels_def,
-        create_time = timestamp_def,
-        update_time = timestamp_def,
-        desc = desc_def,
     },
+    additionalProperties = false,
 }
 
 _M.upstream = upstream_schema
@@ -742,7 +757,14 @@ local secret_uri_schema = {
 _M.ssl = {
     type = "object",
     properties = {
+        -- metadata
         id = id_schema,
+        desc = desc_def,
+        labels = labels_def,
+        create_time = timestamp_def,
+        update_time = timestamp_def,
+
+        -- properties
         type = {
             description = "ssl certificate type, " ..
                             "server to server certificate, " ..
@@ -814,7 +836,6 @@ _M.ssl = {
             },
             required = {"ca"},
         },
-        labels = labels_def,
         status = {
             description = "ssl status, 1 to enable, 0 to disable",
             type = "integer",
@@ -830,8 +851,6 @@ _M.ssl = {
                 enum = {"TLSv1.1", "TLSv1.2", "TLSv1.3"}
             },
         },
-        create_time = timestamp_def,
-        update_time = timestamp_def
     },
     ["if"] = {
         properties = {
@@ -852,13 +871,20 @@ _M.ssl = {
 
 
 
+-- TODO: Design a plugin resource registration framework used by plugins and move the proto
+--       resource to grpc-transcode plugin, which should not be an APISIX core resource
 _M.proto = {
     type = "object",
     properties = {
+        -- metadata
         id = id_schema,
+        name = rule_name_def,
         desc = desc_def,
+        labels = labels_def,
         create_time = timestamp_def,
         update_time = timestamp_def,
+
+        -- properties
         content = {
             type = "string", minLength = 1, maxLength = 1024*1024
         }
@@ -871,10 +897,13 @@ _M.proto = {
 _M.global_rule = {
     type = "object",
     properties = {
+        -- metadata
         id = id_schema,
-        plugins = plugins_schema,
         create_time = timestamp_def,
-        update_time = timestamp_def
+        update_time = timestamp_def,
+
+        -- properties
+        plugins = plugins_schema,
     },
     required = {"id", "plugins"},
     additionalProperties = false,
@@ -923,12 +952,16 @@ local xrpc_protocol_schema = {
 _M.stream_route = {
     type = "object",
     properties = {
+        -- metadata
         id = id_schema,
+        name = rule_name_def,
         desc = desc_def,
+        labels = labels_def,
         create_time = timestamp_def,
         update_time = timestamp_def,
+
+        -- properties
         remote_addr = remote_addr_def,
-        labels = labels_def, -- The ingress project need this field
         server_addr = {
             description = "server IP",
             type = "string",
@@ -977,15 +1010,18 @@ _M.plugins = {
 _M.plugin_config = {
     type = "object",
     properties = {
+        -- metadata
+        id = id_schema,
         name = {
             type = "string",
         },
-        id = id_schema,
         desc = desc_def,
-        plugins = plugins_schema,
         labels = labels_def,
         create_time = timestamp_def,
-        update_time = timestamp_def
+        update_time = timestamp_def,
+
+        -- properties
+        plugins = plugins_schema,
     },
     required = {"id", "plugins"},
     additionalProperties = false,
@@ -995,12 +1031,16 @@ _M.plugin_config = {
 _M.consumer_group = {
     type = "object",
     properties = {
+        -- metadata
         id = id_schema,
+        name = rule_name_def,
         desc = desc_def,
-        plugins = plugins_schema,
         labels = labels_def,
         create_time = timestamp_def,
-        update_time = timestamp_def
+        update_time = timestamp_def,
+
+        -- properties
+        plugins = plugins_schema,
     },
     required = {"id", "plugins"},
     additionalProperties = false,
diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md
index e6099e65a..f75a82037 100644
--- a/docs/en/latest/admin-api.md
+++ b/docs/en/latest/admin-api.md
@@ -891,6 +891,7 @@ Credential resource request address:/apisix/admin/consumers/{username}/credent
 | Parameter   | Required | Type        | Description                                                | Example                                         |
 | ----------- |-----| ------- |------------------------------------------------------------|-------------------------------------------------|
 | plugins     | False    | Plugin      | Auth plugins configuration.                                |                                                 |
+| name        | False    | Auxiliary   | Identifier for the Credential.                             | credential_primary                              |
 | desc        | False    | Auxiliary   | Description of usage scenarios.                            | credential xxxx                                 |
 | labels      | False    | Match Rules | Attributes of the Credential specified as key-value pairs. | {"version":"v2","build":"16","env":"production"} |
 
@@ -1275,6 +1276,7 @@ For notes on ID syntax please refer to: [ID Syntax](#quick-note-on-id-syntax)
 | client.depth | False    | Certificate              | Sets the verification depth in client certificate chains. Defaults to 1. Requires OpenResty 1.19+.             |                                                  |
 | client.skip_mtls_uri_regex | False    | An array of regular expressions, in PCRE format              | Used to match URI, if matched, this request bypasses the client certificate checking, i.e. skip the MTLS.             | ["/hello[0-9]+", "/foobar"]                                                |
 | snis         | True, only if `type` is `server`     | Match Rules              | A non-empty array of HTTPS SNI                                                                                 |                                                  |
+| desc         | False    | Auxiliary                | Description of usage scenarios. | certs for production env |
 | labels       | False    | Match Rules              | Attributes of the resource specified as key-value pairs.                                                       | {"version":"v2","build":"16","env":"production"} |
 | type         | False    | Auxiliary            | Identifies the type of certificate, default  `server`.                                                                             | `client` Indicates that the certificate is a client certificate, which is used when APISIX accesses the upstream; `server` Indicates that the certificate is a server-side certificate, which is used by APISIX when verifying client requests.     |
 | status       | False    | Auxiliary                | Enables the current SSL. Set to `1` (enabled) by default.                                                      | `1` to enable, `0` to disable                    |
@@ -1342,6 +1344,7 @@ Consumer group resource request address: /apisix/admin/consumer_groups/{id}
 | Parameter   | Required | Description                                                                                                        | Example                                          |
 | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |
 | plugins     | True     | Plugins that are executed during the request/response cycle. See [Plugin](terminology/plugin.md) for more. |                                                  |
+| name        | False    | Identifier for the consumer group.                                                                                 | premium-tier                            |
 | desc        | False    | Description of usage scenarios.                                                                                    | customer xxxx                                    |
 | labels      | False    | Attributes of the Consumer group specified as key-value pairs.                                                      | {"version":"v2","build":"16","env":"production"} |
 
@@ -1491,6 +1494,9 @@ Stream Route resource request address:  /apisix/admin/stream_routes/{id}
 
 | Parameter   | Required | Type     | Description                                                         | Example                       |
 | ----------- | -------- | -------- | ------------------------------------------------------------------- | ----------------------------- |
+| name        | False    | Auxiliary | Identifier for the Stream Route.                                   | postgres-proxy                |
+| desc        | False    | Auxiliary | Description of usage scenarios.                                    | proxy endpoint for postgresql |
+| labels      | False    | Match Rules | Attributes of the Proto specified as key-value pairs.    | {"version":"17","service":"user","env":"production"}     |
 | upstream    | False    | Upstream | Configuration of the [Upstream](./terminology/upstream.md). |                               |
 | upstream_id | False    | Upstream | Id of the [Upstream](terminology/upstream.md) service.      |                               |
 | service_id  | False    | String   | Id of the [Service](terminology/service.md) service.        |                               |
@@ -1684,9 +1690,12 @@ Proto resource request address: /apisix/admin/protos/{id}
 
 ### Request Body Parameters
 
-| Parameter | Required | Type    | Description                          | Example                       |
-|-----------|----------|---------|--------------------------------------| ----------------------------- |
-| content   | True     | String  | content of `.proto` or `.pb` files   | See [here](./plugins/grpc-transcode.md#enabling-the-plugin)         |
+| Parameter | Required | Type      | Description                          | Example                       |
+|-----------|----------|-----------|--------------------------------------| ----------------------------- |
+| content   | True     | String    | Content of `.proto` or `.pb` files   | See [here](./plugins/grpc-transcode.md#enabling-the-plugin)         |
+| name      | False    | Auxiliary | Identifier for the Protobuf definition. | user-proto                    |
+| desc      | False    | Auxiliary | Description of usage scenarios.      | protobuf for user service     |
+| labels    | False    | Match Rules | Attributes of the Proto specified as key-value pairs. | {"version":"v2","service":"user","env":"production"}     |
 
 ## Schema validation
 
diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md
index 0bc5a963c..8f78fa16d 100644
--- a/docs/zh/latest/admin-api.md
+++ b/docs/zh/latest/admin-api.md
@@ -904,6 +904,7 @@ Credential 资源请求地址:/apisix/admin/consumers/{username}/credentials/{
 | 名称        | 必选项 | 类型     | 描述                    | 示例值                                             |
 | ----------- |-----| ------- |-----------------------| ------------------------------------------------ |
 | plugins     | 是   | Plugin   | 该 Credential 对应的插件配置。 |                                                  |
+| name        | 否   | 辅助     | 消费者 Credential 名     | credential_primary                               |
 | desc        | 否   | 辅助     | Credential 描述。        |                                                  |
 | labels      | 否   | 匹配规则  | 标识附加属性的键值对。           | {"version":"v2","build":"16","env":"production"} |
 
@@ -1275,6 +1276,7 @@ SSL 资源请求地址:/apisix/admin/ssls/{id}
 | client.depth | 否   | 辅助 |  设置客户端证书校验的深度,默认为 1。该特性需要 OpenResty 为 1.19 及以上版本。 |                                             |
 | client.skip_mtls_uri_regex | 否   | PCRE 正则表达式数组 |  用来匹配请求的 URI,如果匹配,则该请求将绕过客户端证书的检查,也就是跳过 MTLS。 | ["/hello[0-9]+", "/foobar"]                                            |
 | snis        | 是   | 匹配规则       | 非空数组形式,可以匹配多个 SNI。                                                                         |                                                  |
+| desc        | 否   | 辅助          | 证书描述。        | certs for production env                                               |
 | labels      | 否   | 匹配规则       | 标识附加属性的键值对。                                                                                   | {"version":"v2","build":"16","env":"production"} |
 | type        | 否   | 辅助           | 标识证书的类型,默认值为 `server`。                                                                     | `client` 表示证书是客户端证书,APISIX 访问上游时使用;`server` 表示证书是服务端证书,APISIX 验证客户端请求时使用。     |
 | status      | 否   | 辅助           | 当设置为 `1` 时,启用此 SSL,默认值为 `1`。                                                               | `1` 表示启用,`0` 表示禁用                       |
@@ -1342,6 +1344,7 @@ Consumer Group 资源请求地址:/apisix/admin/consumer_groups/{id}
 | 名称      | 必选项  | 类型  | 描述                                          | 示例值 |
 |--------- |--------- |------|----------------------------------------------- |------|
 |plugins  | 是        |Plugin| 插件配置。详细信息请参考 [Plugin](terminology/plugin.md)。 |      |
+|name     | 否        | 辅助 | 消费者组名。            | premium-tier                           |
 |desc     | 否        | 辅助 | 标识描述、使用场景等。                          | Consumer 测试。|
 |labels   | 否        | 辅助 | 标识附加属性的键值对。                          |{"version":"v2","build":"16","env":"production"}|
 
@@ -1500,6 +1503,9 @@ Plugin 资源请求地址:/apisix/admin/stream_routes/{id}
 
 | 名称             | 必选项 | 类型     | 描述                                                                           | 示例值 |
 | ---------------- | ------| -------- | ------------------------------------------------------------------------------| ------  |
+| name             | 否    | 辅助     | Stream 路由名。            | postgres-proxy                                   |
+| desc             | 否    | 辅助     | Stream 路由描述。          | proxy endpoint for postgresql                    |
+| labels           | 否    | 匹配规则  | 标识附加属性的键值对。           | {"version":"17","service":"user","env":"production"} |
 | upstream         | 否    | Upstream | Upstream 配置,详细信息请参考 [Upstream](terminology/upstream.md)。             |         |
 | upstream_id      | 否    | Upstream | 需要使用的 Upstream id,详细信息请 [Upstream](terminology/upstream.md)。       |         |
 | service_id       | 否    | String   | 需要使用的 [Service](terminology/service.md) id.                   |                               |
diff --git a/t/admin/metadata.spec.ts b/t/admin/metadata.spec.ts
new file mode 100644
index 000000000..39dfe1830
--- /dev/null
+++ b/t/admin/metadata.spec.ts
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+import { readFile } from 'node:fs/promises';
+import { resolve } from 'node:path';
+
+import { request as requestAdminAPI } from '../ts/admin_api';
+
+describe('Resource Metadata', () => {
+  describe('Consumer', () => {
+    it('should ensure additionalProperties is false', () =>
+      expect(
+        requestAdminAPI(
+          '/apisix/admin/consumers/jack',
+          'PUT',
+          {
+            username: 'jack',
+            invalid: true,
+          },
+          undefined,
+          { validateStatus: () => true },
+        ),
+      ).resolves.toMatchObject({ status: 400 }));
+
+    it('should accept desc field', () =>
+      expect(
+        requestAdminAPI('/apisix/admin/consumers/jack', 'PUT', {
+          username: 'jack',
+          desc: 'test_desc',
+        }),
+      ).resolves.not.toThrow());
+  });
+
+  describe('Consumer Credentials', () => {
+    it('should ensure additionalProperties is false', () =>
+      expect(
+        requestAdminAPI(
+          '/apisix/admin/consumers/jack/credentials/cred1',
+          'PUT',
+          {
+            plugins: { 'key-auth': { key: 'test' } },
+            invalid: true,
+          },
+          undefined,
+          { validateStatus: () => true },
+        ),
+      ).resolves.toMatchObject({ status: 400 }));
+
+    it('should accept name field', () =>
+      expect(
+        requestAdminAPI(
+          '/apisix/admin/consumers/jack/credentials/cred1',
+          'PUT',
+          {
+            name: 'test_name',
+            plugins: { 'key-auth': { key: 'test' } },
+          },
+        ),
+      ).resolves.not.toThrow());
+  });
+
+  describe('SSL', () => {
+    const path = resolve(__dirname, '../certs/');
+    let cert: string;
+    let key: string;
+
+    beforeAll(async () => {
+      cert = await readFile(resolve(path, 'apisix.crt'), 'utf-8');
+      key = await readFile(resolve(path, 'apisix.key'), 'utf-8');
+    });
+
+    it('should ensure additionalProperties is false', () =>
+      expect(
+        requestAdminAPI(
+          '/apisix/admin/ssls/ssl1',
+          'PUT',
+          { sni: 'test.com', cert, key, invalid: true },
+          undefined,
+          { validateStatus: () => true },
+        ),
+      ).resolves.toMatchObject({ status: 400 }));
+
+    it('should accept desc field', () =>
+      expect(
+        requestAdminAPI('/apisix/admin/ssls/ssl1', 'PUT', {
+          desc: 'test_desc',
+          sni: 'test.com',
+          cert,
+          key,
+        }),
+      ).resolves.not.toThrow());
+  });
+
+  describe('Proto', () => {
+    it('should ensure additionalProperties is false', () =>
+      expect(
+        requestAdminAPI(
+          '/apisix/admin/protos/proto1',
+          'PUT',
+          { content: 'syntax = "proto3";', invalid: true },
+          undefined,
+          { validateStatus: () => true },
+        ),
+      ).resolves.toMatchObject({ status: 400 }));
+
+    it('should accept name/labels field', () =>
+      expect(
+        requestAdminAPI('/apisix/admin/protos/proto1', 'PUT', {
+          name: 'test_name',
+          labels: { test: 'test' },
+          content: 'syntax = "proto3";',
+        }),
+      ).resolves.not.toThrow());
+  });
+
+  describe('Stream Route', () => {
+    it('should ensure additionalProperties is false', () =>
+      expect(
+        requestAdminAPI(
+          '/apisix/admin/stream_routes/sr1',
+          'PUT',
+          { upstream: { nodes: { '127.0.0.1:5432': 1 } }, invalid: true },
+          undefined,
+          { validateStatus: () => true },
+        ),
+      ).resolves.toMatchObject({ status: 400 }));
+
+    it('should accept name field', () =>
+      expect(
+        requestAdminAPI('/apisix/admin/stream_routes/sr1', 'PUT', {
+          name: 'test_name',
+          upstream: { nodes: { '127.0.0.1:5432': 1 } },
+        }),
+      ).resolves.not.toThrow());
+  });
+
+  describe('Consumer Group', () => {
+    it('should ensure additionalProperties is false', () =>
+      expect(
+        requestAdminAPI(
+          '/apisix/admin/consumer_groups/cg1',
+          'PUT',
+          { plugins: {}, invalid: true },
+          undefined,
+          { validateStatus: () => true },
+        ),
+      ).resolves.toMatchObject({ status: 400 }));
+
+    it('should accept name field', () =>
+      expect(
+        requestAdminAPI('/apisix/admin/consumer_groups/cg1', 'PUT', {
+          name: 'test_name',
+          plugins: {},
+        }),
+      ).resolves.not.toThrow());
+  });
+});
diff --git a/t/admin/metadata.t b/t/admin/metadata.t
new file mode 100644
index 000000000..d27035e23
--- /dev/null
+++ b/t/admin/metadata.t
@@ -0,0 +1,36 @@
+#
+# 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.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+use_hup();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: test
+--- timeout: 15
+--- max_size: 204800
+--- exec
+cd t && pnpm test admin/metadata.spec.ts 2>&1
+--- no_error_log
+failed to execute the script with status
+--- response_body eval
+qr/PASS admin\/metadata.spec.ts/
diff --git a/t/ts/admin_api.ts b/t/ts/admin_api.ts
index cccd91da9..6cc381330 100644
--- a/t/ts/admin_api.ts
+++ b/t/ts/admin_api.ts
@@ -14,22 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import axios, { type Method, type RawAxiosRequestHeaders } from 'axios';
+import axios, {
+  type AxiosRequestConfig,
+  type Method,
+  type RawAxiosRequestHeaders,
+} from 'axios';
 
 export const request = async (
-  uri: string,
+  url: string,
   method: Method = 'GET',
   body?: object,
   headers?: RawAxiosRequestHeaders,
+  config?: AxiosRequestConfig,
 ) => {
   return axios.request({
     method,
     // TODO: use 9180 for admin api
-    url: `http://127.0.0.1:1984/${uri}`,
+    baseURL: 'http://127.0.0.1:1984',
+    url,
     data: body,
     headers: {
       'X-API-KEY': 'edd1c9f034335f136f87ad84b625c8f1',
       ...headers,
     },
+    ...config,
   });
 };