update route_create.sh

This commit is contained in:
Chu Văn Hải committed 2026-06-18 11:29:33 +07:00
1 parent e54e57436b
commit a0315401e8
1 file changed
+146
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env bash
set -euo pipefail
ADMIN_API_URL="${ADMIN_API_URL:-http://localhost:9180}"
ADMIN_API_KEY="${ADMIN_API_KEY:-edd1c9f034335f136f87ad84b625c8f1}"
TENANT_ID="${TENANT_ID:-2081a403-c773-427a-86a4-543f0983d921}"
usage() {
cat <<'EOF'
Usage:
./route_create.sh <route-prefix> <dataverse-host> <client-id> <client-secret>
Params:
route-prefix APISIX public route prefix, for example: ssm-vn-dev
dataverse-host Dataverse host without scheme, for example: tngredev.crm5.dynamics.com
client-id Microsoft Entra application client id
client-secret Microsoft Entra application client secret
Derived values:
route id <route-prefix>-route
route uri /<route-prefix>/*
token cache key dataverse_token_<route-prefix with '-' replaced by '_'>
upstream https://<dataverse-host>
token scope https://<dataverse-host>/.default
Example:
./route_create.sh ssm-vn-dev tngredev.crm5.dynamics.com 2f680fb4-35b8-4be2-99ee-a3d183b607c9 'YOUR_CLIENT_SECRET'
Optional env:
ADMIN_API_URL Default: http://localhost:9180
ADMIN_API_KEY Default: edd1c9f034335f136f87ad84b625c8f1
TENANT_ID Default: 2081a403-c773-427a-86a4-543f0983d921
EOF
}
if [ "$#" -ne 4 ]; then
echo "ERROR: Missing required params. Expected 4 params, got $#." >&2
echo >&2
usage >&2
exit 1
fi
ROUTE_PREFIX="$1"
DATAVERSE_HOST="$2"
CLIENT_ID="$3"
CLIENT_SECRET="$4"
if ! [[ "$ROUTE_PREFIX" =~ ^[A-Za-z0-9][A-Za-z0-9_-]*$ ]]; then
echo "ERROR: route-prefix is invalid: $ROUTE_PREFIX" >&2
echo "Use only letters, numbers, hyphen, and underscore. Example: ssm-vn-dev" >&2
exit 1
fi
if [[ "$DATAVERSE_HOST" == http://* || "$DATAVERSE_HOST" == https://* || "$DATAVERSE_HOST" == */* ]]; then
echo "ERROR: dataverse-host must be host only, without scheme/path." >&2
echo "Example: tngredev.crm5.dynamics.com" >&2
exit 1
fi
json_escape() {
sed \
-e 's/\\/\\\\/g' \
-e 's/"/\\"/g' \
-e ':a;N;$!ba;s/\n/\\n/g'
}
lua_escape() {
sed \
-e 's/\\/\\\\/g' \
-e 's/"/\\"/g' \
-e ':a;N;$!ba;s/\n/\\n/g'
}
ROUTE_ID="${ROUTE_PREFIX}-route"
TOKEN_SUFFIX="${ROUTE_PREFIX//-/_}"
TOKEN_CACHE_KEY="dataverse_token_${TOKEN_SUFFIX}"
ROUTE_URI="/${ROUTE_PREFIX}/*"
REWRITE_REGEX="^/${ROUTE_PREFIX}(/.*)$"
UPSTREAM_NODE="${DATAVERSE_HOST}:443"
TOKEN_SCOPE="https://${DATAVERSE_HOST}/.default"
TOKEN_CACHE_KEY_LUA="$(printf '%s' "$TOKEN_CACHE_KEY" | lua_escape)"
TENANT_ID_LUA="$(printf '%s' "$TENANT_ID" | lua_escape)"
CLIENT_ID_LUA="$(printf '%s' "$CLIENT_ID" | lua_escape)"
CLIENT_SECRET_LUA="$(printf '%s' "$CLIENT_SECRET" | lua_escape)"
TOKEN_SCOPE_LUA="$(printf '%s' "$TOKEN_SCOPE" | lua_escape)"
LUA_FUNCTION="return function(conf, ctx) local core = require(\"apisix.core\"); local http = require(\"resty.http\"); local cache = ngx.shared.oauth_cache; if not cache then core.log.error(\"oauth_cache not defined in config.yaml\"); return end; local token = cache:get(\"${TOKEN_CACHE_KEY_LUA}\"); if not token then local httpc = http.new(); httpc:set_timeout(5000); local tenant_id = \"${TENANT_ID_LUA}\"; local client_id = \"${CLIENT_ID_LUA}\"; local client_secret = \"${CLIENT_SECRET_LUA}\"; local scope = \"${TOKEN_SCOPE_LUA}\"; local body = \"grant_type=client_credentials&client_id=\" .. ngx.escape_uri(client_id) .. \"&client_secret=\" .. ngx.escape_uri(client_secret) .. \"&scope=\" .. ngx.escape_uri(scope); local res, err = httpc:request_uri(\"https://login.microsoftonline.com/\" .. tenant_id .. \"/oauth2/v2.0/token\", { method = \"POST\", body = body, headers = { [\"Content-Type\"] = \"application/x-www-form-urlencoded\" }, ssl_verify = false }); if not res or res.status ~= 200 then core.log.error(\"Failed to fetch Azure AD token: \", err or (res and res.body)); return core.response.exit(500, { error = \"Auth failed to upstream provider\" }) end; local data, decode_err = core.json.decode(res.body); if not data or not data.access_token then core.log.error(\"Failed to decode token response\"); return core.response.exit(500, { error = \"Invalid token response\" }) end; token = data.access_token; local expires_in = data.expires_in or 3600; cache:set(\"${TOKEN_CACHE_KEY_LUA}\", token, expires_in - 60) end; ngx.req.set_header(\"Authorization\", \"Bearer \" .. token) end"
ROUTE_ID_JSON="$(printf '%s' "$ROUTE_ID" | json_escape)"
ROUTE_URI_JSON="$(printf '%s' "$ROUTE_URI" | json_escape)"
REWRITE_REGEX_JSON="$(printf '%s' "$REWRITE_REGEX" | json_escape)"
UPSTREAM_NODE_JSON="$(printf '%s' "$UPSTREAM_NODE" | json_escape)"
LUA_FUNCTION_JSON="$(printf '%s' "$LUA_FUNCTION" | json_escape)"
PAYLOAD_FILE="$(mktemp)"
trap 'rm -f "$PAYLOAD_FILE"' EXIT
cat > "$PAYLOAD_FILE" <<EOF
{
"uri": "${ROUTE_URI_JSON}",
"name": "${ROUTE_ID_JSON}",
"plugins": {
"key-auth": {},
"proxy-rewrite": {
"regex_uri": [
"${REWRITE_REGEX_JSON}",
"\$1"
]
},
"serverless-pre-function": {
"phase": "access",
"functions": [
"${LUA_FUNCTION_JSON}"
]
},
"limit-count": {
"count": 100,
"time_window": 60,
"rejected_code": 429
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"${UPSTREAM_NODE_JSON}": 1
},
"pass_host": "node",
"scheme": "https"
}
}
EOF
echo "Creating/updating APISIX route:"
echo " route id: ${ROUTE_ID}"
echo " public uri: ${ROUTE_URI}"
echo " upstream: https://${DATAVERSE_HOST}"
echo " rewrite: /${ROUTE_PREFIX}/... -> /..."
echo " token cache key: ${TOKEN_CACHE_KEY}"
echo
curl -i "${ADMIN_API_URL}/apisix/admin/routes/${ROUTE_ID}" \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H 'Content-Type: application/json' \
-X PUT \
--data-binary "@${PAYLOAD_FILE}"