Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apisix/admin/standalone.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -128,6 +129,18 @@ local function update(ctx)
return core.response.exit(204)
end

-- resolve ${{VAR}} / $ENV:// references before validation, so that the
-- schema check and the stored config both see the resolved values. The
-- file-based standalone path resolves these in config_yaml.parse(); the
-- Admin API path decodes JSON straight into the config and would otherwise
-- validate and store the literal "${{...}}" string.
local resolved, resolve_err = file.resolve_conf_var(req_body)
if not resolved then
return core.response.exit(400, {
error_msg = "failed to resolve variables in config: " .. resolve_err
})
end

local valid, error_msg = validate_configuration(req_body, false)
if not valid then
return core.response.exit(400, { error_msg = error_msg })
Expand Down
92 changes: 92 additions & 0 deletions t/admin/standalone.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -944,4 +944,96 @@ 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');
});

it('coerces a resolved ${{VAR}} to its native type before validation', async () => {
mockDigest += 1;
const config = {
routes: [
{
id: 'r_var_typed',
uri: '/r_var_typed',
upstream: {
nodes: { '127.0.0.1:1980': 1 },
type: 'roundrobin',
// retries is an integer field. The reference resolves to the
// default "2", which resolve_conf_var coerces to the number 2;
// a literal string "2" would fail integer schema validation
// with 400, so a 202 proves the value was coerced.
retries: '${{STANDALONE_ENV_RETRIES:=2}}',
},
plugins: {
'proxy-rewrite': { uri: '/hello' },
},
},
],
};
const putResp = await client.put(ENDPOINT, config, {
headers: { [HEADER_DIGEST]: mockDigest },
});
expect(putResp.status).toEqual(202);

const resp = await client.get('/r_var_typed');
expect(resp.status).toEqual(200);
expect(resp.data).toEqual('hello world\n');
});

it('rejects config with an unresolvable ${{VAR}} reference', async () => {
mockDigest += 1;
const config = {
routes: [
{
id: 'r_var_bad',
uri: '/r_var_bad',
upstream: {
nodes: { '127.0.0.1:1980': 1 },
type: 'roundrobin',
},
// No matching environment variable and no ":=default", so
// resolution fails and the push is rejected with 400 rather than
// storing a literal "${{...}}".
plugins: {
'proxy-rewrite': { uri: '${{STANDALONE_ENV_UNDEFINED}}' },
},
},
],
};
const resp = await client
.put(ENDPOINT, config, { headers: { [HEADER_DIGEST]: mockDigest } })
.catch((err) => err.response);
expect(resp.status).toEqual(400);
expect(resp.data.error_msg).toContain('failed to resolve variables in config');
});
});
});
Loading