diff --git a/apisix/admin/standalone.lua b/apisix/admin/standalone.lua index 46f9a8001bbf..c2c7144ccb69 100644 --- a/apisix/admin/standalone.lua +++ b/apisix/admin/standalone.lua @@ -28,6 +28,7 @@ local events = require("apisix.events") local core = require("apisix.core") local config_yaml = require("apisix.core.config_yaml") local config_validate = require("apisix.admin.config_validate") +local file = require("apisix.cli.file") local ALL_RESOURCE_KEYS = config_validate.get_all_resource_keys() @@ -327,6 +328,16 @@ function _M.init_worker() -- this table is generated by json decode, so there is no need to clone it config[METADATA_LAST_MODIFIED] = nil config[METADATA_DIGEST] = nil + + -- resolve ${{VAR}} / $ENV:// references in the config pushed via the + -- Admin API. The file-based standalone path resolves these in + -- config_yaml.parse(), but the Admin API path decodes JSON straight + -- into _update_config and would otherwise keep them literal. + local ok, err = file.resolve_conf_var(config) + if not ok then + core.log.error("failed to resolve variables in config: ", err) + return + end config_yaml._update_config(config) end events:register(update_config, EVENT_UPDATE, EVENT_UPDATE) diff --git a/t/admin/standalone.spec.ts b/t/admin/standalone.spec.ts index 5a71c4122a91..a0c79a077665 100644 --- a/t/admin/standalone.spec.ts +++ b/t/admin/standalone.spec.ts @@ -944,4 +944,37 @@ describe('Validate API - Standalone', () => { }); }); }); + + describe('Variable resolution', () => { + it('resolves ${{VAR}} references in config pushed via the Admin API', async () => { + mockDigest += 1; + const config = { + routes: [ + { + id: 'r_var', + uri: '/r_var', + upstream: { + nodes: { '127.0.0.1:1980': 1 }, + type: 'roundrobin', + }, + // The proxy-rewrite uri uses a ${{VAR:=default}} reference. The + // gateway must resolve it to the default ("/hello"); if it were + // left literal, the upstream would receive "${{...}}" instead of + // "/hello" and would not return the hello body. + plugins: { + 'proxy-rewrite': { uri: '${{STANDALONE_ENV_TEST:=/hello}}' }, + }, + }, + ], + }; + const putResp = await client.put(ENDPOINT, config, { + headers: { [HEADER_DIGEST]: mockDigest }, + }); + expect(putResp.status).toEqual(202); + + const resp = await client.get('/r_var'); + expect(resp.status).toEqual(200); + expect(resp.data).toEqual('hello world\n'); + }); + }); });