Compare commits

...

187 Commits

Author SHA1 Message Date
澄潭
8f1f935bc9 docs: add AGENTS.md with repo/plugin index for AI agents
Signed-off-by: 澄潭 <zty98751@alibaba-inc.com>
2026-07-07 00:54:23 +08:00
澄潭
4db97b6a36 test: build #4034 repro plugin in CI (VERSION -alpha)
CI build-wasm-plugins.sh only compiles wasm-go plugins whose VERSION ends
in -alpha; VERSION was "1" so the plugin was skipped and the file:// wasm was
absent in CI, making the wasm filter fail closed (HTTP 500 on every request).
Bump VERSION to 1.0.0-alpha so CI builds the plugin.

Signed-off-by: 澄潭 <zty98751@alibaba-inc.com>
2026-07-07 00:39:16 +08:00
澄潭
bf30043807 test: add #4034 conformance repro (redis-failure inject CPU spin)
Adds a wasm-go conformance case reproducing #4034: a deferred async
redis-failure callback synchronously calls injectEncodedDataToFilterChain,
which pre-fix makes WasmBase::doAfterVmCallActions re-queue an action forever
and spins a worker at 100% CPU. Redis is pointed at an unroutable endpoint so
the failure callbacks fire under concurrency. With the drain-to-local fix the
gateway stays responsive (200); a spinning worker would fail via timeout.

Validated end-to-end against the rebuilt gateway image.

Signed-off-by: 澄潭 <zty98751@alibaba-inc.com>
2026-07-06 21:48:47 +08:00
澄潭
0a8aea12d6 fix: point ENVOY_LATEST_IMAGE_TAG at rebuilt gateway for #4034 drain-to-local
Use the gateway image built from the envoy submodule that carries the
proxy-wasm-cpp-host doAfterVmCallActions drain-to-local fix, so e2e and
dev installs exercise the #4034 CPU-spin fix.

Signed-off-by: 澄潭 <zty98751@alibaba-inc.com>
2026-07-06 21:36:14 +08:00
澄潭
91244c578a fix: bump envoy submodule to include doAfterVmCallActions drain-to-local (#4034)
Advances the envoy/envoy submodule from f49d591e to f468a1a3 (higress-group/envoy
envoy-1.36 tip), which carries the proxy_wasm_cpp_host pin bump (higress-group/envoy#28)
picking up higress-group/proxy-wasm-cpp-host#12.

That fix makes WasmBase::doAfterVmCallActions reentry-safe (drain the
after_vm_call_actions_ queue into a stack-local before iterating), resolving the
worker-thread CPU 100% infinite loop when a Wasm plugin re-enters via
onRedisCallFailure + injectEncodedDataToFilterChain (#4034).

Signed-off-by: 澄潭 <zty98751@alibaba-inc.com>
2026-07-06 15:45:03 +08:00
澄潭
d5f2b624fb feat: initialize issue-spec workflow for Higress (#4059)
Signed-off-by: johnlanni <johnlanni@users.noreply.github.com>
Co-authored-by: johnlanni <johnlanni@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 21:55:53 +08:00
澄潭
6c98ea74e0 fix: avoid waiting for missing request bodies in transformer (#4024)
Signed-off-by: johnlanni <zty98751@alibaba-inc.com>
2026-06-25 17:36:01 +08:00
EndlessSeeker
39ec41aab6 Release v2.2.3 (#4019)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-06-25 10:34:30 +08:00
澄潭
910c26ec14 chore: update envoy submodule (#4017)
Signed-off-by: zty98751 <zty98751@alibaba-inc.com>
2026-06-24 19:06:16 +08:00
EndlessSeeker
582af464c0 Revert "fix: Skip TLS certificate verification for HTTPS upstreams" (#4016)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-06-24 15:39:32 +08:00
EndlessSeeker
04389fd0c6 fix: harden cluster key rate limit cookie parsing (#4012)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-06-24 15:03:05 +08:00
EndlessSeeker
10498a2c86 fix(oidc): fail closed when verifier is unavailable (#4013)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
Co-authored-by: Jingze <52855280+Jing-ze@users.noreply.github.com>
2026-06-24 14:22:24 +08:00
yangjuan
e958290283 feat(ai-context-limit): add context window limit wasm plugin (#4000)
Signed-off-by: Cai Rui <cairui@U-7VTK6WQN-2207.local>
2026-06-24 13:40:51 +08:00
DENG
6d1c55c1fb fix(ai-proxy): add ids for Vertex tool calls (#3990)
Signed-off-by: DENG <33118163+XinhhD@users.noreply.github.com>
Co-authored-by: woody <yaodiwu618@gmail.com>
2026-06-23 20:34:54 +08:00
9r0k
e57f18e581 feat(ai-proxy): vLLM passthrough for Anthropic Messages and newer OpenAI endpoints (#3989)
Signed-off-by: Rand01ph <tanyawei1991@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: woody <yaodiwu618@gmail.com>
2026-06-23 19:34:54 +08:00
Kent Dong
1694f48fd9 fix(ai-cache): handle SSE first chunk with role only (fixes #3953) (#3962)
Signed-off-by: CH3CHO <ch3cho@qq.com>
Co-authored-by: woody <yaodiwu618@gmail.com>
2026-06-23 17:38:50 +08:00
cyberslack_lee
7c94b5a822 fix: fix some typo errors (#3988)
Co-authored-by: woody <yaodiwu618@gmail.com>
2026-06-23 11:08:44 +08:00
geekspeng
ab0085362c fix(helm): apply imagePullPolicy to controller container and promtail sidecar (#4002)
Signed-off-by: geekspeng <geekspeng@icloud.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 22:33:03 +08:00
zijiren
9c13b6418c feat: Enhance SSL passthrough support (#3943)
Signed-off-by: zijiren233 <pyh1670605849@gmail.com>
2026-06-22 21:06:42 +08:00
EndlessSeeker
f060c9f51d fix: preserve ingress load balancer hostnames (#3994)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-06-22 11:34:38 +08:00
Heracles
bd6039f53b fix(helm): use Chart.Version as default plugin-server image tag (#3998)
Signed-off-by: yqz <2678785492@qq.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-22 09:28:36 +08:00
小画家
36bd830029 bugfix: fix golang-filter architecture mismatch during local gateway build on arm64 macOS (#3905)
Signed-off-by: 胥寅 <xuuyin@dingtalk.com>
Co-authored-by: 胥寅 <xuuyin@dingtalk.com>
2026-06-21 18:13:58 +08:00
澄潭
4db4633560 chore: update envoy submodule (#3987)
Signed-off-by: zty98751 <zty98751@alibaba-inc.com>
2026-06-17 20:28:15 +08:00
EndlessSeeker
efaef2e3d0 Support configurable GatewayClass isolation (#3981)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-06-17 20:27:45 +08:00
geekspeng
e758504d72 fix(helm): move imagePullPolicy from PodSpec to container level in gateway pod template (#3924)
Signed-off-by: geekspeng <geekspeng@icloud.com>
2026-06-17 16:22:51 +08:00
DENG
3c3a067f07 fix(ai-proxy): include tool call type in Claude stream deltas (#3985)
Signed-off-by: DENG <33118163+XinhhD@users.noreply.github.com>
Co-authored-by: woody <yaodiwu618@gmail.com>
2026-06-17 12:18:18 +08:00
DENG
7e11e2f320 fix(ai-proxy): preserve Vertex thoughtSignature in OpenAI tool calls (#3973)
Signed-off-by: DENG <33118163+XinhhD@users.noreply.github.com>
2026-06-17 11:40:38 +08:00
DENG
7abf27a2a3 fix(ai-proxy): clean client Authorization header for api-key providers (#3956)
Signed-off-by: DENG <33118163+XinhhD@users.noreply.github.com>
Co-authored-by: woody <yaodiwu618@gmail.com>
2026-06-16 16:19:37 +08:00
EndlessSeeker
d1f95f6e54 fix: set gateway status address type by value (#3980)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-06-16 13:41:25 +08:00
EndlessSeeker
83c12d571d feat(helm): allow skipping IngressClass creation (#3979)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-06-16 11:26:56 +08:00
Jingze
45fc5a31bc test(wasm-plugins): lift unit-test coverage to ≥90% across 9 plugins (#3879)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
Co-authored-by: woody <yaodiwu618@gmail.com>
2026-06-15 20:33:10 +08:00
Jingze
3065d4e071 test(traffic-tag): cover operator matrix, weight CDF boundaries, and parser reject paths (#3878)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
2026-06-15 20:32:38 +08:00
Jingze
c65d4cd938 test(hmac-auth-apisix): cover algorithm matrix, clock skew, and auth edge cases (#3875)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 20:31:02 +08:00
Jingze
547b7bf45a test(transformer): add end-to-end coverage and fix silent error wrapping (#3873)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
2026-06-15 20:29:51 +08:00
Jingze
bf0b1e96c5 test(wasm-go/mcp): expand unit test coverage for mcp-server framework (#3871)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 20:29:20 +08:00
EndlessSeeker
c69526b30e fix(helm): disable alpha Gateway API watches by default (#3971)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-06-15 18:52:28 +08:00
EndlessSeeker
73e7510eca fix: merge InferencePool route configs during HTTPRoute merge (#3964) 2026-06-12 18:05:24 +08:00
yangjuan
2897c1e83a feat(model-router): add keepOriginalModelName option to preserve full model name (#3916)
Signed-off-by: Cai Rui <yangjuan.cr@alibaba-inc.com>
2026-06-07 15:49:45 +08:00
FAUST
b69b5c7c10 doc: add missed chinese readme for extensions (#3890)
Signed-off-by: zhoujinyu <2319109590@qq.com>
2026-06-07 12:26:08 +08:00
Jingze
57db9ccc46 fix(oidc): fix Set-Cookie header corruption during session refresh (#3928)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-05 17:58:20 +08:00
澄潭
137d76332e fix: rebuild mcp filter on high memory usage (#3922)
Signed-off-by: johnlanni <zty98751@alibaba-inc.com>
2026-06-05 10:57:43 +08:00
澄潭
d888ab23de fix(wasm): remove request-count rebuild triggers (#3923)
Signed-off-by: johnlanni <zty98751@alibaba-inc.com>
2026-06-05 10:57:24 +08:00
Jingze
29b5a533e6 fix(oidc): bump oauth2-proxy to fix nil panic in verifier callback (#3914)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-04 10:52:24 +08:00
Jingze
071086904d fix(ai-proxy): strip anthropic-beta and anthropic-version headers in vertex passthrough (#3904)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
2026-06-02 20:26:30 +08:00
JianweiWang
58ffdae6ba feat(ai-security-guard): add Embedding API content detection support (#3895)
Signed-off-by: root <jianwei.wjw@alibaba-inc.com>
2026-06-02 12:11:00 +08:00
JianweiWang
a45b1dde6c fix(ai-statistics): skip empty streaming model values (#3851)
Signed-off-by: jianwei.wjw <jianwei.wjw@alibaba-inc.com>
2026-06-02 12:10:46 +08:00
zat366
52c99eb27d feat(ai-load-balancer): add cluster_hash load balancing policy with FNV-1a consistent hashing (#3898)
Signed-off-by: zat366 <authentic.zhao@gmail.com>
2026-06-01 10:19:46 +08:00
JianweiWang
c21a38e783 feat(ai-security-guard): structured x_higress deny response, error-path metrics, and AI logging (#3894)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: rinfx <yucheng.lxr@alibaba-inc.com>
2026-05-29 10:45:10 +08:00
Jingze
385f8d8b4e fix(ai-proxy): natively route Anthropic Messages to vertex :rawPredict (#3860)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:54:56 +08:00
Betula-L
a86aaadaa4 feat(jwt-auth): support remote JWKS (#3838)
Signed-off-by: Betula-L <6059935+Betula-L@users.noreply.github.com>
Co-authored-by: Betula-L <6059935+Betula-L@users.noreply.github.com>
2026-05-25 16:04:10 +08:00
Kent Dong
e6fc09b14f fix: Fix test cases in registry/nacos/mcpserver/watcher_test.go (#3869)
Signed-off-by: CH3CHO <ch3cho@qq.com>
2026-05-25 15:15:03 +08:00
Kent Dong
b8ff815100 fix: Fix the description of basePath related fields of ai-proxy plugin (#3870)
Signed-off-by: CH3CHO <ch3cho@qq.com>
2026-05-25 15:02:39 +08:00
zat366
632c6ca655 fix(ai-proxy): use HasSuffix instead of Contains in claude.GetApiName to prevent sub-path misidentification (#3839)
Signed-off-by: zat366 <authentic.zhao@gmail.com>
2026-05-25 14:34:45 +08:00
Ghxst
8a0f8a8208 feature: support plural key-auth credentials (#3849)
Signed-off-by: GHX5T-SOL <200635707+GHX5T-SOL@users.noreply.github.com>
Co-authored-by: GHX5T-SOL <200635707+GHX5T-SOL@users.noreply.github.com>
2026-05-24 17:56:58 +08:00
Jingze
b99e0c5027 fix(ai-proxy): re-anchor X-HI-ORIGINAL-AUTH on first hop to fix cascaded-gateway 401 (#3864)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:01:08 +08:00
muicoder
34040d1b99 fix #3858 arm64 image corruption caused by overriding BuildKit TARGETARCH (#3859)
Signed-off-by: muicoder <muicoder@gmail.com>
2026-05-22 11:17:56 +08:00
github-actions[bot]
870ed27435 Add release notes (#3854)
Co-authored-by: EndlessSeeker <153817598+EndlessSeeker@users.noreply.github.com>
2026-05-21 17:40:55 +08:00
EndlessSeeker
1ccafab24b Release v2.2.2 (#3852)
Signed-off-by: 如漫 <zhaoyuanxiao.zyx@alibaba-inc.com>
Signed-off-by: EndlessSeeker <1766508902@qq.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-21 14:50:10 +08:00
woody
37b038f797 feat: route bedrock messages through mantle (#3820)
Signed-off-by: wydream <yaodiwu618@gmail.com>
Signed-off-by: woody <yaodiwu618@gmail.com>
2026-05-20 18:13:04 +08:00
woody
739d47ba9c feat(ai-proxy): add cooldownDuration support for failover token recovery (#3700)
Signed-off-by: wydream <yaodiwu618@gmail.com>
Signed-off-by: woody <yaodiwu618@gmail.com>
2026-05-20 18:11:11 +08:00
woody
e7651f3d3e fix(ai-proxy): support azure openai v1 service urls (#3765)
Signed-off-by: wydream <yaodiwu618@gmail.com>
Co-authored-by: EndlessSeeker <153817598+EndlessSeeker@users.noreply.github.com>
2026-05-20 13:48:35 +08:00
woody
e1e631263c Fix/claude thinking tool call conversion (#3756)
Signed-off-by: wydream <yaodiwu618@gmail.com>
2026-05-20 11:34:07 +08:00
Kent Dong
f1dfc8f3d2 fix: Fix a field tag typo in ai-proxy (#3829)
Signed-off-by: CH3CHO <ch3cho@qq.com>
2026-05-19 15:02:36 +08:00
EndlessSeeker
73c93a18b0 docs: add OpenSSF Best Practices badge to README (#3830)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-18 09:46:00 +08:00
Kent Dong
ba774da55e feat(ext-auth): add support for allowed properties forwarding in external authorization requests (#3694)
Signed-off-by: CH3CHO <ch3cho@qq.com>
2026-05-15 16:03:50 +08:00
rinfx
e497d8017a feat(model-mapper): sync model header on remap and disable reroute (#3827)
Signed-off-by: 钰诚 <yucheng.lxr@alibaba-inc.com>
2026-05-15 14:38:54 +08:00
woody
a93847e07f Add Kling provider support (#3742)
Signed-off-by: wydream <yaodiwu618@gmail.com>
2026-05-14 16:18:00 +08:00
澄潭
b032f344e6 feat: add nginx rewrite compatible wasm plugin (#3823)
Signed-off-by: johnlanni <zty98751@alibaba-inc.com>
2026-05-14 11:24:58 +08:00
woody
c7eed0c0c1 fix(vertex): inject api key for express raw endpoints (#3777)
Signed-off-by: wydream <yaodiwu618@gmail.com>
Co-authored-by: EndlessSeeker <153817598+EndlessSeeker@users.noreply.github.com>
2026-05-13 10:18:29 +08:00
zat366
f8d81a7eb4 feat(ai-quota): add enable_path_suffixes configuration and update rel… (#3748)
Signed-off-by: zat366 <authentic.zhao@gmail.com>
Co-authored-by: Kent Dong <ch3cho@qq.com>
Co-authored-by: EndlessSeeker <153817598+EndlessSeeker@users.noreply.github.com>
2026-05-12 17:44:10 +08:00
Srikanth Patchava
29da03c371 fix: nil guards, safe type assertions, panic prevention, and rate limiter plugin (#3757)
Signed-off-by: Srikanth Patchava <spatchava@meta.com>
Signed-off-by: Srikanth Patchava <srpatcha@users.noreply.github.com>
Co-authored-by: Srikanth Patchava <srpatcha@users.noreply.github.com>
2026-05-12 11:00:37 +08:00
Jingze
1c4fe1c9f9 test(ai-proxy): expand wasm integration tests, coverage, and provider matrix (#3790)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
2026-05-12 10:20:08 +08:00
Jingze
678ea5660e fix(wasm): resolve stale route_name in wasm context after reroute (#3576)
Signed-off-by: jingze <daijingze.djz@alibaba-inc.com>
2026-05-12 10:19:36 +08:00
Kent Dong
148ef6905e fix: fix logging for unsupported upstream protocols in EnvoyFilter construction (#3801)
Signed-off-by: CH3CHO <ch3cho@qq.com>
2026-05-12 09:30:49 +08:00
Betula-L
6d50e2da42 fix(ai-proxy): preserve empty Claude tool inputs (#3799)
Signed-off-by: Betula-L <6059935+Betula-L@users.noreply.github.com>
Co-authored-by: Betula-L <6059935+Betula-L@users.noreply.github.com>
2026-05-09 21:35:35 +08:00
Betula-L
b77a074831 fix(ai-proxy): preserve Bedrock Claude reasoning blocks (#3788)
Signed-off-by: Betula-L <6059935+Betula-L@users.noreply.github.com>
Co-authored-by: Betula-L <6059935+Betula-L@users.noreply.github.com>
2026-05-08 10:27:48 +08:00
Betula-L
6199fe414d bugfix: map bedrock tool-call indexes and tool_choice (#3786)
Signed-off-by: Betula-L <6059935+Betula-L@users.noreply.github.com>
Co-authored-by: Betula-L <6059935+Betula-L@users.noreply.github.com>
2026-05-06 19:48:42 +08:00
rinfx
4aba4a9860 add cached token usage when converting openai to claude (#3766)
Signed-off-by: 钰诚 <yucheng.lxr@alibaba-inc.com>
2026-05-06 10:53:08 +08:00
Kent Dong
4e0d691432 fix: fix non-json logs with log_as_json enabled (#3779)
Signed-off-by: CH3CHO <ch3cho@qq.com>
2026-04-30 18:03:33 +08:00
Kent Dong
74c68180c8 fix: Skip TLS certificate verification for HTTPS upstreams (#3770)
Signed-off-by: CH3CHO <ch3cho@qq.com>
2026-04-29 17:15:54 +08:00
EndlessSeeker
9b9f6b900a docs: update SECURITY.md, CONTRIBUTING docs for CNCF/OpenSSF compliance (#3764)
Signed-off-by: EndlessSeeker <1766508902@qq.com>
2026-04-28 17:28:26 +08:00
JianweiWang
5173b4b2b8 feat(ai-security-guard): add fallback JSON paths for response content extraction (#3738)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: rinfx <yucheng.lxr@alibaba-inc.com>
2026-04-28 14:58:59 +08:00
澄潭
1d33067372 docs: add MAINTAINERS.md (#3754)
Signed-off-by: johnlanni <zty98751@alibaba-inc.com>
2026-04-24 16:25:24 +08:00
澄潭
fb8e1ef33f Update README.md 2026-04-21 14:50:23 +08:00
澄潭
a93f77d838 feat(ai-prompt-decorator): add literal/regex replace rules for message content (#3739) 2026-04-20 20:52:36 +08:00
Kent Dong
90ccfc7ec5 feat(envoy-build): add patch command check and improve error handling logic (#3734) 2026-04-20 13:41:42 +08:00
Kent Dong
d72363d8d1 fix(build): Make sure golang-filter is built with the correct TARGET_ARCH (#3682)
Co-authored-by: EndlessSeeker <153817598+EndlessSeeker@users.noreply.github.com>
2026-04-20 11:24:20 +08:00
Kent Dong
784cffa5e2 fix: Add default values to Dockerfile.higress to fix build warnings (#3425) 2026-04-20 10:52:15 +08:00
Kent Dong
8b8a710305 refactor(ai-cache): update README files to match latest config parsing code (#3730) 2026-04-20 09:45:39 +08:00
woody
65405965b6 fix(ai-proxy): harden Claude stream conversion compatibility (#3733) 2026-04-18 21:27:27 +08:00
JianweiWang
9128cbf729 fix(ai-security-guard): 移除Suggestion=block的兜底逻辑,改为基于阈值判断 || fix(ai-security-guard): Remove the cover-up logic of Suggestion=block and change it to based on threshold judgment (#3731)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-17 14:42:22 +08:00
woody
94b40aab9a feat(ai-proxy): support Qwen reranks and conversations paths (#3724) 2026-04-16 15:52:58 +08:00
woody
26e17c48eb fix(ai-proxy): migrate Qwen responses endpoint to new compatible path (#3722) 2026-04-16 10:13:25 +08:00
JianweiWang
b1187cc14d feat(ai-security-guard): enhance risk action resolution and support sensitive data masking (#3690)
Co-authored-by: rinfx <yucheng.lxr@alibaba-inc.com>
2026-04-15 11:14:56 +08:00
johnlanni
e2beb6cd45 Revert "feat(model-mapper): 新增 modelToHeader 配置项并优化 header 更新逻辑 || feat(model-mapper): Added modelToHeader configuration item and optimized header update logic (#3689)"
This reverts commit 60ce07d297.
2026-04-11 07:57:38 +08:00
github-actions[bot]
899633ea5e Add release notes (#3696)
Co-authored-by: EndlessSeeker <153817598+EndlessSeeker@users.noreply.github.com>
2026-04-10 13:56:25 +08:00
woody
bf96860a78 fix(vertex): add API Key auth for Vertex Raw Express Mode and fix tok… (#3695) 2026-04-10 09:55:27 +08:00
EndlessSeeker
2c15f97246 Release 2.2.1 (#3692) 2026-04-09 20:11:46 +08:00
icylord
69523292cb feat(helm): Optimize Helm charts, fine-tune the imagePullPolicy configuration for gateway, plugin server, and controller pods (#3550) 2026-04-08 21:47:48 +08:00
rinfx
60ce07d297 feat(model-mapper): 新增 modelToHeader 配置项并优化 header 更新逻辑 || feat(model-mapper): Added modelToHeader configuration item and optimized header update logic (#3689) 2026-04-08 17:12:46 +08:00
rinfx
228eb27e6a feat(ai-proxy): 新增 providerBasePath 配置并优化 providerDomain 处理方式 (#3686) 2026-04-08 15:23:15 +08:00
JianweiWang
1c9e981bf2 feat(ai-security-guard): replace denyMessage with structured DenyResponseBody (#3642)
Co-authored-by: rinfx <yucheng.lxr@alibaba-inc.com>
2026-04-01 19:38:01 +08:00
woody
89587c1c9b fix: remove non-standard thinking/reasoning_max_tokens from Claude-to… (#3667) 2026-04-01 09:40:14 +08:00
woody
cd8ed99db5 Vertex structured outputs (#3649) 2026-03-30 17:26:55 +08:00
woody
889ea67013 feat(provider): 优化 Azure multipart 处理 || feat(provider): Optimize Azure multipart processing (#3651) 2026-03-30 13:45:41 +08:00
Kent Dong
83461887dc fix: Remove unused pilot values in the helm chart (#3657) 2026-03-30 13:45:13 +08:00
Kent Dong
983c57f404 fix: Fix a bug in template processor occurred when mixing default and non-default namespaces in one config (#3652) 2026-03-27 20:02:22 +08:00
rinfx
231ba1cd23 support gemini & claude domain setting (#3638) 2026-03-26 11:12:30 +08:00
Jholly
3fc01913cf docs: replace broken higress.io links (#3644)
Co-authored-by: Kent Dong <kentstl@163.com>
2026-03-26 09:39:44 +08:00
shiyan
213286bb9e fix(kingress): include header match in duplicate-route dedup key (#3580)
Fix incorrect duplicate-route detection in the KIngress controller that caused header-differentiated routes to be silently dropped.
2026-03-24 16:17:46 +08:00
shiyan
c75f741104 fix: update kingress status logic (#3575)
Signed-off-by: shi_yan <shi_yan@trip.com>
2026-03-24 14:58:01 +08:00
rinfx
cce53203ce ai-statistics: increase default value_length_limit and emit AILog during streaming usage (#3624) 2026-03-23 14:04:02 +08:00
daofeng
23a0299d3b feat: add CI workflow to build and push plugin-server image on release (#3632) 2026-03-22 22:11:03 +08:00
澄潭
36a1680952 Update model configurations for new GPT-5.4 versions 2026-03-22 16:16:49 +08:00
Kent Dong
ca22fcb90b feat: Add responses to the default path suffix list of model-router and model-mapper (#3585) 2026-03-20 13:52:49 +08:00
Sunrisea
70ec36107a chore(nacos): upgrade nacos-sdk-go/v2 from v2.3.2 to v2.3.5 (#3587) 2026-03-20 13:46:41 +08:00
澄潭
ca7ee6ef5f feat(ai-proxy): add promoteThinkingOnEmpty and hiclawMode config options (#3625)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 00:39:47 +08:00
woody
045238944d Feat/new api path support (#3620) 2026-03-19 19:25:55 +08:00
woody
62df71aadf feat(ai-proxy): 添加Amazon Bedrock Prompt Cache保留策略配置及优化缓存处理逻辑 (#3609) 2026-03-18 20:37:04 +08:00
woody
8961db2e90 fix: vertex streaming chunk parse (#3599) 2026-03-16 21:13:18 +08:00
woody
94f0d7179f fix: bedrock canonical URI sig (#3590) 2026-03-16 14:54:19 +08:00
澄潭
f1e305844e feat(ai-proxy): add mergeConsecutiveMessages option to merge consecutive same-role messages (#3598)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 14:28:17 +08:00
woody
68d6090e36 feat(bedrock): prompt caching params transform (#3563) 2026-03-12 17:44:42 +08:00
LiuXiang
65aba909d7 Fix duplicate import (#3582)
Signed-off-by: LiuXiang <lx1036@126.com>
2026-03-11 09:42:21 +08:00
Kent Dong
528e6c9908 rel: Upgrade console to v2.2.1 and release higress v2.2.1 (#3570) 2026-03-09 11:52:59 +08:00
Damos chen
13b808c1e4 fix(mcp-session): send SSE endpoint event via local goroutine InjectData (#3567) 2026-03-05 20:17:16 +08:00
johnlanni
aa502e7e62 remove openai-compat in developerRoleSupportedProviders 2026-03-05 10:09:47 +08:00
johnlanni
2e3f6868df Revert "openai compatiable developer replace"
This reverts commit 6c9747d778.
2026-03-05 10:07:54 +08:00
johnlanni
6c9747d778 openai compatiable developer replace 2026-03-04 19:57:31 +08:00
woody
c12183cae5 修复 ai-proxy 插件 Bedrock Provider 在 AWS AK/SK 鉴权模式下仅对部分 API 进行 SigV4 签名的问题 || Fixed the problem of ai-proxy plug-in Bedrock Provider only performing SigV4 signature on some APIs in AWS AK/SK authentication mode (#3549) 2026-03-02 09:55:31 +08:00
woody
e2a22d1171 [ai-proxy] vertex image edits & variations (#3536) 2026-02-27 10:18:30 +08:00
Jingze
e9aecb6e1f fix(ai-proxy): update anthropic messages endpoint path for qwen provider (#3530) 2026-02-25 10:26:25 +08:00
github-actions[bot]
b3fb6324a4 Add release notes (#3524)
Co-authored-by: johnlanni <6763318+johnlanni@users.noreply.github.com>
2026-02-22 20:14:09 +08:00
澄潭
8576128e4c feat(ai-statistics): add Claude/Anthropic streaming tool calls parsing support (#3523) 2026-02-21 14:14:22 +08:00
澄潭
caa5317723 feat: share hub parameter between deployments and plugins with separate namespaces (#3521) 2026-02-20 23:30:48 +08:00
澄潭
093ef9a2c0 Update index.ts 2026-02-19 12:47:34 +08:00
澄潭
9346f1340b refactor: migrate MCP SDK to main repo (#3516) 2026-02-16 23:39:18 +08:00
澄潭
87c6cc9c9f Fix model pattern for Dashscope entry 2026-02-16 22:40:37 +08:00
澄潭
ac29ba6984 Rename qwen3-coder-plus to qwen3.5-plus 2026-02-16 22:24:58 +08:00
澄潭
1c847dd553 feat(ai-proxy): strip dynamic cch field from billing header to enable caching (#3518) 2026-02-15 23:57:08 +08:00
澄潭
a07f5024a9 fix(ai-proxy): convert OpenAI tool role to Claude user role with tool_result (#3517) 2026-02-15 22:45:09 +08:00
澄潭
814c3307ba fix(ai-statistics): lightweight mode should include question and model (#3513) 2026-02-15 20:49:57 +08:00
澄潭
b76a3aca5e feat(ai-statistics): add lightweight mode with use_default_response_attributes (#3512) 2026-02-15 17:23:54 +08:00
澄潭
28df33c596 feat(ai-statistics): add system field support for Claude /v1/messages API (#3511) 2026-02-15 14:16:19 +08:00
澄潭
8e7292c42e fix(ai-proxy): fix Claude protocol conversion issues (#3510) 2026-02-15 13:52:26 +08:00
澄潭
d03932b3ea fix(ai-proxy): add streaming tool_calls support for Claude provider (#3507) 2026-02-15 08:48:20 +08:00
澄潭
5a2ff8c836 fix(ai-proxy): convert Claude tool_use stop_reason to OpenAI tool_calls format (#3506) 2026-02-14 21:52:25 +08:00
澄潭
6f8ef2ff69 fix(ai-statistics): use RuleAppend as default for streaming answer extraction (#3505)这个 2026-02-14 13:58:55 +08:00
澄潭
67e2913f3d fix(ai-proxy): preserve text content alongside tool_result in Claude to OpenAI conversion (#3503) 2026-02-14 12:12:07 +08:00
澄潭
e996194228 fix(ai-proxy): add missing event field in Claude streaming response (#3502) 2026-02-14 09:55:14 +08:00
澄潭
95f86d7ab5 feat(ai-proxy): add consumer affinity for stateful APIs (#3499) 2026-02-14 09:22:12 +08:00
澄潭
5d5d20df1f fix(ci): pin GitHub Actions runner to ubuntu-22.04 (#3500) 2026-02-14 07:17:10 +08:00
澄潭
1ddc07992c Update index.ts 2026-02-13 21:41:19 +08:00
澄潭
13ed2284ae fix(ai-proxy): fix claude system content null serialization (#3496) 2026-02-13 20:32:37 +08:00
澄潭
f9c7527753 Update index.ts 2026-02-13 09:40:50 +08:00
澄潭
c2be0e8c9a fix(ai-statistics): add ValueSource to built-in attributes for streaming body buffering (#3491) 2026-02-13 09:03:06 +08:00
澄潭
927fb52309 Update sync-skills-to-oss.yaml 2026-02-13 00:01:32 +08:00
澄潭
c0761c4553 Update SKILL.md 2026-02-12 23:38:07 +08:00
澄潭
4f857597da docs: optimize provider list in OpenClaw integration skill (#3490) 2026-02-12 23:36:42 +08:00
澄潭
0d45ce755f feat(skill): add z.ai domain and code plan mode options (#3489) 2026-02-12 23:21:49 +08:00
澄潭
44d688a168 feat(ai-proxy): add zhipu provider enhancements (#3488) 2026-02-12 22:19:13 +08:00
澄潭
0d9354da16 fix(skill): correct model reference prefix for higress provider (#3485) 2026-02-12 19:52:51 +08:00
澄潭
65834bff21 fix(skill): update higress-openclaw-integration to use dedicated install directory (#3484) 2026-02-12 19:39:43 +08:00
澄潭
668c2b3669 Update SKILL.md 2026-02-12 18:35:24 +08:00
澄潭
ff4de901e7 Update SKILL.md 2026-02-12 18:28:29 +08:00
澄潭
a1967adb94 fix: use absolute path for packaging skills (#3483) 2026-02-12 18:23:16 +08:00
澄潭
f6cb3031fe feat: optimize skills packaging in OSS sync workflow (#3482) 2026-02-12 18:19:57 +08:00
澄潭
d4a0665957 feat: add GitHub Action to sync skills to OSS (#3481) 2026-02-12 18:15:03 +08:00
澄潭
2c7771da42 Update index.ts 2026-02-12 16:56:23 +08:00
澄潭
75c6fbe090 Rename plugin ID from 'higress-ai-gateway' to 'higress' 2026-02-12 16:50:51 +08:00
澄潭
b153d08610 Update README.md 2026-02-12 16:50:17 +08:00
澄潭
de633d8610 Rename plugin ID from 'higress-ai-gateway' to 'higress' 2026-02-12 16:48:41 +08:00
澄潭
f2e4942f00 Update package.json 2026-02-12 16:48:26 +08:00
澄潭
1b3a8b762b docs: improve OpenClaw integration prompt for configuration updates (#3480) 2026-02-12 16:41:31 +08:00
澄潭
c885b89d03 Update SKILL.md 2026-02-12 16:24:01 +08:00
澄潭
ce4dff9887 feat(ai-proxy): convert developer role to system for unsupported providers (#3479) 2026-02-12 16:14:46 +08:00
澄潭
6935a44d53 docs: mark OpenClaw commands as interactive in SKILL.md (#3478) 2026-02-12 15:49:13 +08:00
澄潭
b33e2be5e9 Update SKILL.md 2026-02-12 15:43:01 +08:00
澄潭
d2385f1b30 fix: remove duplicate /v1 path in OpenClaw plugin baseUrl (#3477) 2026-02-12 15:40:01 +08:00
澄潭
ef5e3ee31b Update index.ts 2026-02-12 15:26:55 +08:00
澄潭
d2b0885236 Update index.ts 2026-02-12 15:07:59 +08:00
澄潭
6cb48247fd Delete compatibility information from README.md
Removed compatibility section for OpenClaw and Higress AI Gateway.
2026-02-12 14:42:26 +08:00
澄潭
773f639260 Update SKILL.md 2026-02-12 14:41:18 +08:00
澄潭
fe58ce3943 Update SKILL.md 2026-02-12 14:37:29 +08:00
澄潭
0dbc056ce9 docs: improve higress-openclaw-integration skill for better usability (#3476) 2026-02-12 14:34:03 +08:00
github-actions[bot]
3bf39b60ea Add release notes (#3468)
Co-authored-by: johnlanni <6763318+johnlanni@users.noreply.github.com>
2026-02-12 14:02:50 +08:00
澄潭
e9bb5d3255 refactor: rename skill to higress-openclaw-integration and update model configs (#3475) 2026-02-12 14:02:29 +08:00
440 changed files with 66465 additions and 5975 deletions

View File

@@ -0,0 +1,35 @@
---
name: issue-spec-apply
description: Implement PROCESS comments for an issue-spec change and keep PR traceability synchronized.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Apply
Use when the user asks for /issue-spec:apply, issue-spec apply, or implementing PROCESS/TASK scopes from an issue-spec change.
## Steps
1. Read proposal/design/implement issue context and list typed comments with issue-spec comment list --json.
2. Confirm issue-spec auth status --json includes the expected GitHub backend. Local gh-authenticated sessions can use the native gh backend; keep ISSUE_SPEC_TOKEN="$(gh auth token)" only as an older-version or forced-rest compatibility path.
3. Create or update PROCESS comments with owner agent, scope, dependencies, write ownership, and status.
4. Split non-trivial work into independent worker PROCESS nodes when file/module ownership does not overlap; execute independent workers in parallel when available.
5. Add dedicated review PROCESS nodes for non-trivial changes. Review PROCESS nodes should own review scopes such as CLI/API behavior, workflow docs, tests, compatibility, or security-sensitive surfaces.
6. Link each PROCESS to its TASK comments with issue-spec link.
7. Implement the code changes for one PROCESS scope at a time, or integrate completed worker outputs by dependency order.
8. Link every worker and review PROCESS to the PR with issue-spec pr link-process.
9. Add PR rationale comments on key changed lines with issue-spec pr rationale, each linked to a SPEC comment.
10. Mark PROCESS comments done only after implementation/review work and focused verification evidence exist.
## Coordinator DAG Execution
1. Build the ready set from PROCESS nodes whose dependencies are done.
2. Keep immediate blocking work local when the next step depends on it.
3. Spawn or assign independent worker agents only when their write ownership is disjoint.
4. Spawn or assign independent review agents only when their review scopes are disjoint.
5. Integrate completed outputs by dependency order and update PROCESS evidence before marking done.

View File

@@ -0,0 +1,24 @@
---
name: issue-spec-archive
description: Create the post-merge durable spec archive PR for an issue-spec change.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Archive
Use when the user asks for /issue-spec:archive, issue-spec archive, or creating the post-merge durable spec PR.
## Steps
1. Confirm the implementation PR is merged.
2. Create the durable spec PR:
issue-spec archive durable-spec --repo higress-group/higress --proposal <issue> --capability <capability> --create-pr --branch issue-spec/durable-spec-<capability> --json
3. Review the durable spec PR for long-lived behavior only. Do not copy process records, review findings, or verification logs into durable specs.
4. After durable spec PR merge, keep proposal/design/implement issues as audit history unless the project policy says to close them.

View File

@@ -0,0 +1,58 @@
---
name: issue-spec-github
description: Use GitHub CLI for GitHub issues, pull requests, CI runs, and API queries that issue-spec does not wrap.
license: MIT
compatibility: Requires GitHub CLI (gh).
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# GitHub CLI
Use the `gh` CLI to interact with GitHub repositories, issues, pull requests, CI, and API endpoints.
## When To Use
- Checking PR status, reviews, mergeability, or CI checks.
- Creating, viewing, updating, closing, or commenting on GitHub issues.
- Listing or inspecting pull requests, workflow runs, releases, labels, or repository metadata.
- Calling GitHub API endpoints with `gh api` when issue-spec does not provide a dedicated command.
## When Not To Use
- Local git operations such as commit, branch, fetch, merge, or push. Use `git` directly.
- Non-GitHub repositories. Use the matching provider CLI instead.
- Complex code review across local diffs. Read the repository files directly and use issue-spec review commands for traceable findings.
## Setup
```bash
gh auth login
gh auth status
```
## Common Commands
```bash
gh issue list --repo owner/repo --state open
gh issue view 42 --repo owner/repo --json number,title,state,url,body
gh issue comment 42 --repo owner/repo --body "Comment body"
gh pr list --repo owner/repo
gh pr view 17 --repo owner/repo --json number,title,state,headRefName,baseRefName,url
gh pr checks 17 --repo owner/repo
gh run list --repo owner/repo --limit 10
gh run view <run-id> --repo owner/repo --log-failed
gh api repos/owner/repo/labels --jq '.[].name'
```
## Notes
- Always pass `--repo owner/repo` when the current directory is not definitely inside the target repository.
- Use GitHub URLs directly when convenient, for example `gh pr view https://github.com/owner/repo/pull/17`.
- Prefer structured output with `--json` and `--jq` when another command or agent step consumes the result.
- issue-spec owns the proposal, design, implement, typed comment, review, verify, and archive workflow state. Use `gh` for adjacent GitHub operations that are outside issue-spec's command surface.

View File

@@ -0,0 +1,37 @@
---
name: issue-spec-propose
description: Create or continue proposal, SPEC, QUESTION, design, and TASK artifacts for an issue-spec change.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Propose
Use when the user asks for /issue-spec:propose, issue-spec propose, creating a change proposal, drafting SPEC comments, or preparing design/tasks after questions converge.
## Steps
1. Create the proposal issue:
issue-spec issue create proposal --repo higress-group/higress --change <change-name> --body-file <proposal.md>
2. If the proposal body needs revision after discussion, update it in place:
issue-spec issue update --repo higress-group/higress --issue <proposal-issue> --body-file <proposal.md> --summary "<what changed>"
3. Add SPEC comments with issue-spec comment upsert --type SPEC. SPEC comments must use MUST/SHALL and WHEN/THEN scenarios.
4. Add QUESTION comments for unresolved behavior with issue-spec question create and resolve blocking questions before design.
5. Create the design issue after SPEC/QUESTION convergence:
issue-spec issue create design --repo higress-group/higress --change <change-name> --proposal <proposal-issue-or-url> --body-file <design.md>
6. Add TASK comments with issue-spec comment upsert --type TASK and link every TASK to covered SPEC comments with issue-spec link.
7. Create the implement issue once tasks are ready:
issue-spec issue create implement --repo higress-group/higress --change <change-name> --proposal <proposal-issue-or-url> --design <design-issue-or-url> --body-file <implement.md>
8. Run issue-spec verify-links and fix missing backlinks before implementation.

View File

@@ -0,0 +1,32 @@
---
name: issue-spec-review
description: Review an issue-spec implementation PR, create PR line findings, reply after fixes, and sync REVIEW comments.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Review
Use when the user asks for /issue-spec:review, issue-spec review, or a PR review gate for an issue-spec implementation.
## Steps
1. Run issue-spec review sync --repo higress-group/higress --pr <number> --implement <issue> --id REVIEW-<n> --json to capture current rationale comments, findings, and checks.
2. For non-trivial PRs, spawn or assign dedicated review agents as review PROCESS owners. Multiple review agents can run in parallel when their review scopes are independent.
3. Give each review agent a concrete scope and expected output: actionable findings only, severity, file/line, linked SPEC, owner PROCESS, and suggested fix.
4. Create actionable PR line findings with issue-spec review finding. Use P0/P1 for blockers and P2 for non-blocking follow-up.
5. Assign every finding to a PROCESS owner. If no findings are found, record that result in REVIEW or VERIFY evidence.
6. After the worker fixes a finding, reply to the original thread with issue-spec review reply --status resolved.
7. Re-run review sync. P0/P1 findings must be resolved before final verify/archive.
## Review DAG Policy
1. Every non-trivial PR should have at least one dedicated review PROCESS node before final verify.
2. Use multiple review agents in parallel when scopes are independent, for example CLI/API behavior, workflow docs, tests, compatibility, or security-sensitive surfaces.
3. A review agent reports findings only; the coordinator converts actionable line findings into issue-spec review finding comments.
4. P0/P1 findings block final verify until the owner PROCESS fixes them and issue-spec review reply records the resolution on the original thread.
5. If a review agent finds no issues, record that result in REVIEW or VERIFY evidence before marking the review PROCESS done.

View File

@@ -0,0 +1,28 @@
---
name: issue-spec-verify
description: Run final issue-spec verification across traceability, questions, review findings, PR rationale, PR checks, and durable spec draft.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Verify
Use when the user asks for /issue-spec:verify, issue-spec verify, or final readiness evidence before merge/archive.
## Steps
1. Run focused project tests and record evidence in VERIFY comments.
2. Run issue-spec verify-links --repo higress-group/higress --proposal <issue> --design <issue> --implement <issue> --json.
3. Render a durable spec draft:
issue-spec archive durable-spec --repo higress-group/higress --proposal <issue> --capability <capability> --output /tmp/<capability>-spec.md --json
4. Run final verify:
issue-spec verify --repo higress-group/higress --proposal <issue> --design <issue> --implement <issue> --pr <pr> --durable-spec /tmp/<capability>-spec.md --json
5. Final verify must fail if blocking questions, missing links, missing PROCESS rationale, open P0/P1 findings, failed or pending PR checks, or durable spec omissions exist.

View File

@@ -0,0 +1,53 @@
---
name: issue-spec-workflow
description: Use issue-spec to run an issue-native OpenSpec-style workflow with GitHub issues, typed comments, PR review comments, final verification, and durable spec archive PRs.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Workflow
Use this skill for issue-native OpenSpec work. Active change artifacts live in GitHub issues and issue comments; durable specs are repository files created after implementation merge.
## Start
1. Run issue-spec auth status --json and confirm the active auth source and GitHub backend.
2. Run issue-spec status --repo higress-group/higress --proposal <issue> --design <issue> --implement <issue> --json when issues already exist.
3. For new work, create proposal, design, and implement issues with issue-spec issue create and pass --body-file with concrete markdown content.
4. When an issue body changes, update it in place with issue-spec issue update --body-file and include --summary for the human-readable audit trail.
5. Store requirements, tasks, process ownership, review, and verify evidence as typed comments.
## GitHub Backend
- Local agents may rely on native GitHub CLI support: when no ISSUE_SPEC_TOKEN, GH_TOKEN, GITHUB_TOKEN, keyring token, or issue-spec config token is present and gh auth status --active succeeds for the target host, issue-spec auto-selects the gh backend.
- Explicit env or stored issue-spec tokens keep the rest backend under auto selection. Set ISSUE_SPEC_GITHUB_BACKEND=rest or ISSUE_SPEC_GITHUB_BACKEND=gh only when a workflow needs deterministic backend selection.
- The gh backend proxies GitHub API operations through gh api and uses gh --hostname for Enterprise hosts. It does not replace local git commands.
- ISSUE_SPEC_API_URL applies to the rest backend. Forced gh mode should be used only with hosts that gh can address.
- Use ISSUE_SPEC_TOKEN="$(gh auth token)" only for older issue-spec versions or when deliberately forcing rest while sourcing the token from gh.
## Rules
- Create SPEC comments before design; each SPEC must be testable and include WHEN/THEN scenarios.
- Do not leave active proposal/design/implement issue bodies as TBD placeholders.
- Resolve blocking QUESTION comments before design/tasks, or explicitly record accepted assumptions.
- Link SPEC <-> TASK and TASK <-> PROCESS with issue-spec link.
- Link every PROCESS to the implementation PR with issue-spec pr link-process.
- For non-trivial changes, include review PROCESS nodes in the DAG; review agents are scheduled like worker agents and can run in parallel when their review scopes are independent.
- Small changes may stay coordinator-only, but record the serial execution decision in the implement or VERIFY evidence.
- Before human review, add PR rationale comments with issue-spec pr rationale for every active PROCESS.
- Use issue-spec review finding for PR line findings and issue-spec review reply to close the original thread.
- Run issue-spec review sync and issue-spec verify before declaring ready.
- After the implementation PR merges, create the separate durable spec PR with issue-spec archive durable-spec --create-pr.
## Coordinator DAG Execution
1. Treat PROCESS comments as DAG nodes with explicit owner, dependencies, write or review scope, PR link, and evidence.
2. Select ready PROCESS nodes whose dependencies are done and whose scopes do not overlap.
3. Dispatch independent worker PROCESS nodes in parallel when their file/module ownership is disjoint.
4. Dispatch independent review PROCESS nodes in parallel for non-trivial PRs after PR rationale exists.
5. Integrate completed worker outputs by dependency order; route P0/P1 review findings back to the owner PROCESS.
6. Mark PROCESS nodes done only after their implementation or review evidence is recorded and blocking findings are resolved.

View File

@@ -0,0 +1,31 @@
---
name: "Issue Spec: Apply"
description: "Implement PROCESS comments for an issue-spec change and keep PR traceability synchronized."
category: "Workflow"
tags: ["workflow", "issue-spec"]
---
# Issue Spec Apply
Use when the user asks for /issue-spec:apply, issue-spec apply, or implementing PROCESS/TASK scopes from an issue-spec change.
## Steps
1. Read proposal/design/implement issue context and list typed comments with issue-spec comment list --json.
2. Confirm issue-spec auth status --json includes the expected GitHub backend. Local gh-authenticated sessions can use the native gh backend; keep ISSUE_SPEC_TOKEN="$(gh auth token)" only as an older-version or forced-rest compatibility path.
3. Create or update PROCESS comments with owner agent, scope, dependencies, write ownership, and status.
4. Split non-trivial work into independent worker PROCESS nodes when file/module ownership does not overlap; execute independent workers in parallel when available.
5. Add dedicated review PROCESS nodes for non-trivial changes. Review PROCESS nodes should own review scopes such as CLI/API behavior, workflow docs, tests, compatibility, or security-sensitive surfaces.
6. Link each PROCESS to its TASK comments with issue-spec link.
7. Implement the code changes for one PROCESS scope at a time, or integrate completed worker outputs by dependency order.
8. Link every worker and review PROCESS to the PR with issue-spec pr link-process.
9. Add PR rationale comments on key changed lines with issue-spec pr rationale, each linked to a SPEC comment.
10. Mark PROCESS comments done only after implementation/review work and focused verification evidence exist.
## Coordinator DAG Execution
1. Build the ready set from PROCESS nodes whose dependencies are done.
2. Keep immediate blocking work local when the next step depends on it.
3. Spawn or assign independent worker agents only when their write ownership is disjoint.
4. Spawn or assign independent review agents only when their review scopes are disjoint.
5. Integrate completed outputs by dependency order and update PROCESS evidence before marking done.

View File

@@ -0,0 +1,20 @@
---
name: "Issue Spec: Archive"
description: "Create the post-merge durable spec archive PR for an issue-spec change."
category: "Workflow"
tags: ["workflow", "issue-spec"]
---
# Issue Spec Archive
Use when the user asks for /issue-spec:archive, issue-spec archive, or creating the post-merge durable spec PR.
## Steps
1. Confirm the implementation PR is merged.
2. Create the durable spec PR:
issue-spec archive durable-spec --repo higress-group/higress --proposal <issue> --capability <capability> --create-pr --branch issue-spec/durable-spec-<capability> --json
3. Review the durable spec PR for long-lived behavior only. Do not copy process records, review findings, or verification logs into durable specs.
4. After durable spec PR merge, keep proposal/design/implement issues as audit history unless the project policy says to close them.

View File

@@ -0,0 +1,33 @@
---
name: "Issue Spec: Propose"
description: "Create or continue proposal, SPEC, QUESTION, design, and TASK artifacts for an issue-spec change."
category: "Workflow"
tags: ["workflow", "issue-spec"]
---
# Issue Spec Propose
Use when the user asks for /issue-spec:propose, issue-spec propose, creating a change proposal, drafting SPEC comments, or preparing design/tasks after questions converge.
## Steps
1. Create the proposal issue:
issue-spec issue create proposal --repo higress-group/higress --change <change-name> --body-file <proposal.md>
2. If the proposal body needs revision after discussion, update it in place:
issue-spec issue update --repo higress-group/higress --issue <proposal-issue> --body-file <proposal.md> --summary "<what changed>"
3. Add SPEC comments with issue-spec comment upsert --type SPEC. SPEC comments must use MUST/SHALL and WHEN/THEN scenarios.
4. Add QUESTION comments for unresolved behavior with issue-spec question create and resolve blocking questions before design.
5. Create the design issue after SPEC/QUESTION convergence:
issue-spec issue create design --repo higress-group/higress --change <change-name> --proposal <proposal-issue-or-url> --body-file <design.md>
6. Add TASK comments with issue-spec comment upsert --type TASK and link every TASK to covered SPEC comments with issue-spec link.
7. Create the implement issue once tasks are ready:
issue-spec issue create implement --repo higress-group/higress --change <change-name> --proposal <proposal-issue-or-url> --design <design-issue-or-url> --body-file <implement.md>
8. Run issue-spec verify-links and fix missing backlinks before implementation.

View File

@@ -0,0 +1,28 @@
---
name: "Issue Spec: Review"
description: "Review an issue-spec implementation PR, create PR line findings, reply after fixes, and sync REVIEW comments."
category: "Workflow"
tags: ["workflow", "issue-spec"]
---
# Issue Spec Review
Use when the user asks for /issue-spec:review, issue-spec review, or a PR review gate for an issue-spec implementation.
## Steps
1. Run issue-spec review sync --repo higress-group/higress --pr <number> --implement <issue> --id REVIEW-<n> --json to capture current rationale comments, findings, and checks.
2. For non-trivial PRs, spawn or assign dedicated review agents as review PROCESS owners. Multiple review agents can run in parallel when their review scopes are independent.
3. Give each review agent a concrete scope and expected output: actionable findings only, severity, file/line, linked SPEC, owner PROCESS, and suggested fix.
4. Create actionable PR line findings with issue-spec review finding. Use P0/P1 for blockers and P2 for non-blocking follow-up.
5. Assign every finding to a PROCESS owner. If no findings are found, record that result in REVIEW or VERIFY evidence.
6. After the worker fixes a finding, reply to the original thread with issue-spec review reply --status resolved.
7. Re-run review sync. P0/P1 findings must be resolved before final verify/archive.
## Review DAG Policy
1. Every non-trivial PR should have at least one dedicated review PROCESS node before final verify.
2. Use multiple review agents in parallel when scopes are independent, for example CLI/API behavior, workflow docs, tests, compatibility, or security-sensitive surfaces.
3. A review agent reports findings only; the coordinator converts actionable line findings into issue-spec review finding comments.
4. P0/P1 findings block final verify until the owner PROCESS fixes them and issue-spec review reply records the resolution on the original thread.
5. If a review agent finds no issues, record that result in REVIEW or VERIFY evidence before marking the review PROCESS done.

View File

@@ -0,0 +1,24 @@
---
name: "Issue Spec: Verify"
description: "Run final issue-spec verification across traceability, questions, review findings, PR rationale, PR checks, and durable spec draft."
category: "Workflow"
tags: ["workflow", "issue-spec"]
---
# Issue Spec Verify
Use when the user asks for /issue-spec:verify, issue-spec verify, or final readiness evidence before merge/archive.
## Steps
1. Run focused project tests and record evidence in VERIFY comments.
2. Run issue-spec verify-links --repo higress-group/higress --proposal <issue> --design <issue> --implement <issue> --json.
3. Render a durable spec draft:
issue-spec archive durable-spec --repo higress-group/higress --proposal <issue> --capability <capability> --output /tmp/<capability>-spec.md --json
4. Run final verify:
issue-spec verify --repo higress-group/higress --proposal <issue> --design <issue> --implement <issue> --pr <pr> --durable-spec /tmp/<capability>-spec.md --json
5. Final verify must fail if blocking questions, missing links, missing PROCESS rationale, open P0/P1 findings, failed or pending PR checks, or durable spec omissions exist.

View File

@@ -1,435 +0,0 @@
---
name: higress-clawdbot-integration
description: "Deploy and configure Higress AI Gateway for Clawdbot/OpenClaw integration. Use when: (1) User wants to deploy Higress AI Gateway, (2) User wants to configure Clawdbot/OpenClaw to use Higress as a model provider, (3) User mentions 'higress', 'ai gateway', 'model gateway', 'AI网关', (4) User wants to set up model routing or auto-routing, (5) User needs to manage LLM provider API keys, (6) User wants to track token usage and conversation history."
---
# Higress AI Gateway Integration
Deploy and configure Higress AI Gateway for Clawdbot/OpenClaw integration with one-click deployment, model provider configuration, auto-routing, and session monitoring.
## Prerequisites
- Docker installed and running
- Internet access to download setup script
- LLM provider API keys (at least one)
## Workflow
### Step 1: Download Setup Script
Download official get-ai-gateway.sh script:
```bash
curl -fsSL https://raw.githubusercontent.com/higress-group/higress-standalone/main/all-in-one/get-ai-gateway.sh -o get-ai-gateway.sh
chmod +x get-ai-gateway.sh
```
### Step 2: Gather Configuration
Ask user for:
1. **LLM Provider API Keys** (at least one required):
**Top Commonly Used Providers:**
- Aliyun Dashscope (Qwen): `--dashscope-key`
- DeepSeek: `--deepseek-key`
- Moonshot (Kimi): `--moonshot-key`
- Zhipu AI: `--zhipuai-key`
- Claude Code (OAuth mode): `--claude-code-key` (run `claude setup-token` to get token)
- Claude: `--claude-key`
- Minimax: `--minimax-key`
- Azure OpenAI: `--azure-key`
- AWS Bedrock: `--bedrock-key`
- Google Vertex AI: `--vertex-key`
- OpenAI: `--openai-key`
- OpenRouter: `--openrouter-key`
- Grok: `--grok-key`
To configure additional providers beyond the above, run `./get-ai-gateway.sh --help` to view the complete list of supported models and providers.
2. **Port Configuration** (optional):
- HTTP port: `--http-port` (default: 8080)
- HTTPS port: `--https-port` (default: 8443)
- Console port: `--console-port` (default: 8001)
3. **Auto-routing** (optional):
- Enable: `--auto-routing`
- Default model: `--auto-routing-default-model`
### Step 3: Run Setup Script
Run script in non-interactive mode with gathered parameters:
```bash
./get-ai-gateway.sh start --non-interactive \
--dashscope-key sk-xxx \
--openai-key sk-xxx \
--auto-routing \
--auto-routing-default-model qwen-turbo
```
**Automatic Repository Selection:**
The script automatically detects your timezone and selects the geographically closest registry for both:
- **Container image** (`IMAGE_REPO`)
- **WASM plugins** (`PLUGIN_REGISTRY`)
| Region | Timezone Examples | Selected Registry |
|--------|------------------|-------------------|
| China & nearby | Asia/Shanghai, Asia/Hong_Kong, etc. | `higress-registry.cn-hangzhou.cr.aliyuncs.com` |
| Southeast Asia | Asia/Singapore, Asia/Jakarta, etc. | `higress-registry.ap-southeast-7.cr.aliyuncs.com` |
| North America | America/*, US/*, Canada/* | `higress-registry.us-west-1.cr.aliyuncs.com` |
| Others | Default fallback | `higress-registry.cn-hangzhou.cr.aliyuncs.com` |
**Manual Override (optional):**
If you want to use a specific registry:
```bash
IMAGE_REPO="higress-registry.ap-southeast-7.cr.aliyuncs.com/higress/all-in-one" \
PLUGIN_REGISTRY="higress-registry.ap-southeast-7.cr.aliyuncs.com" \
./get-ai-gateway.sh start --non-interactive \
--dashscope-key sk-xxx \
--openai-key sk-xxx
```
### Step 4: Verify Deployment
After script completion:
1. Check container is running:
```bash
docker ps --filter "name=higress-ai-gateway"
```
2. Test gateway endpoint:
```bash
curl http://localhost:8080/v1/models
```
3. Access console (optional):
```
http://localhost:8001
```
### Step 5: Configure Clawdbot/OpenClaw Plugin
If user wants to use Higress with Clawdbot/OpenClaw, install appropriate plugin:
#### Automatic Installation
Detect runtime and install correct plugin version:
```bash
# Detect which runtime is installed
if command -v clawdbot &> /dev/null; then
RUNTIME="clawdbot"
RUNTIME_DIR="$HOME/.clawdbot"
PLUGIN_SRC="scripts/plugin-clawdbot"
elif command -v openclaw &> /dev/null; then
RUNTIME="openclaw"
RUNTIME_DIR="$HOME/.openclaw"
PLUGIN_SRC="scripts/plugin"
else
echo "Error: Neither clawdbot nor openclaw is installed"
exit 1
fi
# Install plugin
PLUGIN_DEST="$RUNTIME_DIR/extensions/higress-ai-gateway"
echo "Installing Higress AI Gateway plugin for $RUNTIME..."
mkdir -p "$(dirname "$PLUGIN_DEST")"
[ -d "$PLUGIN_DEST" ] && rm -rf "$PLUGIN_DEST"
cp -r "$PLUGIN_SRC" "$PLUGIN_DEST"
echo "✓ Plugin installed at: $PLUGIN_DEST"
# Configure provider
echo ""
echo "Configuring provider..."
$RUNTIME models auth login --provider higress
```
The plugin will guide you through an interactive setup for:
1. Gateway URL (default: `http://localhost:8080`)
2. Console URL (default: `http://localhost:8001`)
3. API Key (optional for local deployments)
4. Model list (auto-detected or manually specified)
5. Auto-routing default model (if using `higress/auto`)
### Step 6: Manage API Keys (optional)
After deployment, manage API keys without redeploying:
```bash
# View configured API keys
./get-ai-gateway.sh config list
# Add or update an API key (hot-reload, no restart needed)
./get-ai-gateway.sh config add --provider <provider> --key <api-key>
# Remove an API key (hot-reload, no restart needed)
./get-ai-gateway.sh config remove --provider <provider>
```
**Note:** Changes take effect immediately via hot-reload. No container restart required.
## CLI Parameters Reference
### Basic Options
| Parameter | Description | Default |
|-----------|-------------|---------|
| `--non-interactive` | Run without prompts | - |
| `--http-port` | Gateway HTTP port | 8080 |
| `--https-port` | Gateway HTTPS port | 8443 |
| `--console-port` | Console port | 8001 |
| `--container-name` | Container name | higress-ai-gateway |
| `--data-folder` | Data folder path | ./higress |
| `--auto-routing` | Enable auto-routing feature | - |
| `--auto-routing-default-model` | Default model when no rule matches | - |
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `PLUGIN_REGISTRY` | Registry URL for container images and WASM plugins (auto-selected based on timezone) | `higress-registry.cn-hangzhou.cr.aliyuncs.com` |
**Auto-Selection Logic:**
The registry is automatically selected based on your timezone:
- **China & nearby** (Asia/Shanghai, etc.) → `higress-registry.cn-hangzhou.cr.aliyuncs.com`
- **Southeast Asia** (Asia/Singapore, etc.) → `higress-registry.ap-southeast-7.cr.aliyuncs.com`
- **North America** (America/*, etc.) → `higress-registry.us-west-1.cr.aliyuncs.com`
- **Others** → `higress-registry.cn-hangzhou.cr.aliyuncs.com` (default)
Both container images and WASM plugins use the same registry for consistency.
**Manual Override:**
```bash
PLUGIN_REGISTRY="higress-registry.ap-southeast-7.cr.aliyuncs.com" \
./get-ai-gateway.sh start --non-interactive ...
```
### LLM Provider API Keys
**Top Providers:**
| Parameter | Provider |
|-----------|----------|
| `--dashscope-key` | Aliyun Dashscope (Qwen) |
| `--deepseek-key` | DeepSeek |
| `--moonshot-key` | Moonshot (Kimi) |
| `--zhipuai-key` | Zhipu AI |
| `--claude-code-key` | Claude Code (OAuth mode - run `claude setup-token` to get token) |
| `--claude-key` | Claude |
| `--openai-key` | OpenAI |
| `--openrouter-key` | OpenRouter |
| `--gemini-key` | Google Gemini |
| `--groq-key` | Groq |
**Additional Providers:**
`--doubao-key`, `--baichuan-key`, `--yi-key`, `--stepfun-key`, `--minimax-key`, `--cohere-key`, `--mistral-key`, `--github-key`, `--fireworks-key`, `--togetherai-key`, `--grok-key`, `--azure-key`, `--bedrock-key`, `--vertex-key`
## Managing Configuration
### API Keys
```bash
# List all configured API keys
./get-ai-gateway.sh config list
# Add or update an API key (hot-reload)
./get-ai-gateway.sh config add --provider deepseek --key sk-xxx
# Remove an API key (hot-reload)
./get-ai-gateway.sh config remove --provider deepseek
```
**Supported provider aliases:**
`dashscope`/`qwen`, `moonshot`/`kimi`, `zhipuai`/`zhipu`, `togetherai`/`together`
### Routing Rules
```bash
# Add a routing rule
./get-ai-gateway.sh route add --model claude-opus-4.5 --trigger "深入思考|deep thinking"
# List all rules
./get-ai-gateway.sh route list
# Remove a rule
./get-ai-gateway.sh route remove --rule-id 0
```
See [higress-auto-router](../higress-auto-router/SKILL.md) for detailed documentation.
## Access Logs
Gateway access logs are available at:
```
$DATA_FOLDER/logs/access.log
```
These logs can be used with **agent-session-monitor** skill for token tracking and conversation analysis.
## Related Skills
- **higress-auto-router**: Configure automatic model routing using CLI commands
See: [higress-auto-router](../higress-auto-router/SKILL.md)
- **agent-session-monitor**: Monitor and track token usage across sessions
See: [agent-session-monitor](../agent-session-monitor/SKILL.md)
## Examples
### Example 1: Basic Deployment with Dashscope
**User:** 帮我部署一个Higress AI网关使用阿里云的通义千问
**Steps:**
1. Download script
2. Get Dashscope API key from user
3. Run (script auto-detects timezone and selects optimal registry):
```bash
./get-ai-gateway.sh start --non-interactive \
--dashscope-key sk-xxx
```
**Response:**
```
Auto-detected timezone: Asia/Shanghai
Selected plugin registry: higress-registry.cn-hangzhou.cr.aliyuncs.com
✅ Higress AI Gateway 部署完成!
网关地址: http://localhost:8080/v1/chat/completions
控制台: http://localhost:8001
日志目录: ./higress/logs
已配置的模型提供商:
- Aliyun Dashscope (Qwen)
测试命令:
curl 'http://localhost:8080/v1/chat/completions' \
-H 'Content-Type: application/json' \
-d '{"model": "qwen-turbo", "messages": [{"role": "user", "content": "Hello!"}]}'
```
### Example 2: Full Integration with Clawdbot
**User:** 完整配置Higress和Clawdbot的集成
**Steps:**
1. Deploy Higress AI Gateway (auto-detects timezone)
2. Install and configure Clawdbot plugin
3. Enable auto-routing
4. Set up session monitoring
**Response:**
```
Auto-detected timezone: Asia/Shanghai
Selected plugin registry: higress-registry.cn-hangzhou.cr.aliyuncs.com
✅ Higress AI Gateway 集成完成!
1. 网关已部署:
- HTTP: http://localhost:8080
- Console: http://localhost:8001
- 容器镜像: Hangzhou (自动选择)
- 插件镜像: Hangzhou (自动选择)
2. Clawdbot 插件配置:
Plugin installed at: /root/.clawdbot/extensions/higress-ai-gateway
Run: clawdbot models auth login --provider higress
3. 自动路由:
已启用,使用 model="higress/auto"
4. 会话监控:
日志路径: ./higress/logs/access.log
需要我帮你配置自动路由规则吗?
```
### Example 3: Manage API Keys
**User:** 帮我查看当前配置的API keys并添加一个DeepSeek的key
**Steps:**
1. List current API keys:
```bash
./get-ai-gateway.sh config list
```
2. Add DeepSeek API key:
```bash
./get-ai-gateway.sh config add --provider deepseek --key sk-xxx
```
**Response:**
```
当前配置的API keys:
Aliyun Dashscope (Qwen): sk-ab***ef12
OpenAI: sk-cd***gh34
Adding API key for DeepSeek...
✅ API key updated successfully!
Provider: DeepSeek
Key: sk-xx***yy56
Configuration has been hot-reloaded (no restart needed).
```
### Example 4: North America Deployment
**User:** 帮我部署Higress AI网关
**Context:** User's timezone is America/Los_Angeles
**Steps:**
1. Download script
2. Get API keys from user
3. Run (script auto-detects timezone and selects North America mirror):
```bash
./get-ai-gateway.sh start --non-interactive \
--openai-key sk-xxx \
--openrouter-key sk-xxx
```
**Response:**
```
Auto-detected timezone: America/Los_Angeles
Selected plugin registry: higress-registry.us-west-1.cr.aliyuncs.com
✅ Higress AI Gateway 部署完成!
网关地址: http://localhost:8080/v1/chat/completions
控制台: http://localhost:8001
日志目录: ./higress/logs
镜像优化:
- 容器镜像: North America (基于时区自动选择)
- 插件镜像: North America (基于时区自动选择)
已配置的模型提供商:
- OpenAI
- OpenRouter
```
## Troubleshooting
For detailed troubleshooting guides, see [TROUBLESHOOTING.md](references/TROUBLESHOOTING.md).
Common issues:
- **Container fails to start**: Check Docker status, port availability, and container logs
- **"too many open files" error**: Increase `fs.inotify.max_user_instances` to 8192
- **Gateway not responding**: Verify container status and port mapping
- **Plugin not recognized**: Check installation path and restart runtime
- **Auto-routing not working**: Verify model list and routing rules
- **Timezone detection fails**: Manually set `IMAGE_REPO` environment variable

View File

@@ -1,79 +0,0 @@
# Higress AI Gateway Plugin (Clawdbot)
Clawdbot model provider plugin for Higress AI Gateway with auto-routing support.
## What is this?
This is a TypeScript-based provider plugin that enables Clawdbot to use Higress AI Gateway as a model provider. It provides:
- **Auto-routing support**: Use `higress/auto` to intelligently route requests based on message content
- **Dynamic model discovery**: Auto-detect available models from Higress Console
- **Smart URL handling**: Automatic URL normalization and validation
- **Flexible authentication**: Support for both local and remote gateway deployments
## Files
- **index.ts**: Main plugin implementation
- **package.json**: NPM package metadata and Clawdbot extension declaration
- **clawdbot.plugin.json**: Plugin manifest for Clawdbot
## Installation
This plugin is automatically installed when you use the `higress-clawdbot-integration` skill. See the parent SKILL.md for complete installation instructions.
### Manual Installation
If you need to install manually:
```bash
# Copy plugin files
mkdir -p "$HOME/.clawdbot/extensions/higress-ai-gateway"
cp -r ./* "$HOME/.clawdbot/extensions/higress-ai-gateway/"
# Configure provider
clawdbot models auth login --provider higress
```
## Usage
After installation, configure Higress as a model provider:
```bash
clawdbot models auth login --provider higress
```
The plugin will prompt for:
1. Gateway URL (default: http://localhost:8080)
2. Console URL (default: http://localhost:8001)
3. API Key (optional for local deployments)
4. Model list (auto-detected or manually specified)
5. Auto-routing default model (if using higress/auto)
## Auto-routing
To use auto-routing, include `higress/auto` in your model list during configuration. Then use it in your conversations:
```bash
# Use auto-routing
clawdbot chat --model higress/auto "深入思考 这个问题应该怎么解决?"
# The gateway will automatically route to the appropriate model based on:
# - Message content triggers (configured via higress-auto-router skill)
# - Fallback to default model if no rule matches
```
## Related Resources
- **Parent Skill**: [higress-clawdbot-integration](../SKILL.md)
- **Auto-routing Configuration**: [higress-auto-router](../../higress-auto-router/SKILL.md)
- **Session Monitoring**: [agent-session-monitor](../../agent-session-monitor/SKILL.md)
- **Higress AI Gateway**: https://github.com/higress-group/higress-standalone
## Compatibility
- **Clawdbot**: v2.0.0+
- **Higress AI Gateway**: All versions
## License
Apache-2.0

View File

@@ -1,10 +0,0 @@
{
"id": "higress-ai-gateway",
"name": "Higress AI Gateway",
"description": "Model provider plugin for Higress AI Gateway with auto-routing support",
"providers": ["higress"],
"configSchema": {
"type": "object",
"additionalProperties": true
}
}

View File

@@ -1,284 +0,0 @@
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";
const DEFAULT_GATEWAY_URL = "http://localhost:8080";
const DEFAULT_CONSOLE_URL = "http://localhost:8001";
const DEFAULT_CONTEXT_WINDOW = 128_000;
const DEFAULT_MAX_TOKENS = 8192;
// Common models that Higress AI Gateway typically supports
const DEFAULT_MODEL_IDS = [
// Auto-routing special model
"higress/auto",
// OpenAI models
"gpt-5.2",
"gpt-5-mini",
"gpt-5-nano",
// Anthropic models
"claude-opus-4.5",
"claude-sonnet-4.5",
"claude-haiku-4.5",
// Qwen models
"qwen3-turbo",
"qwen3-plus",
"qwen3-max",
"qwen3-coder-480b-a35b-instruct",
// DeepSeek models
"deepseek-chat",
"deepseek-reasoner",
// Other common models
"kimi-k2.5",
"glm-4.7",
"MiniMax-M2.1",
] as const;
function normalizeBaseUrl(value: string): string {
const trimmed = value.trim();
if (!trimmed) return DEFAULT_GATEWAY_URL;
let normalized = trimmed;
while (normalized.endsWith("/")) normalized = normalized.slice(0, -1);
if (!normalized.endsWith("/v1")) normalized = `${normalized}/v1`;
return normalized;
}
function validateUrl(value: string): string | undefined {
const normalized = normalizeBaseUrl(value);
try {
new URL(normalized);
} catch {
return "Enter a valid URL";
}
return undefined;
}
function parseModelIds(input: string): string[] {
const parsed = input
.split(/[\n,]/)
.map((model) => model.trim())
.filter(Boolean);
return Array.from(new Set(parsed));
}
function buildModelDefinition(modelId: string) {
const isAutoModel = modelId === "higress/auto";
return {
id: modelId,
name: isAutoModel ? "Higress Auto Router" : modelId,
api: "openai-completions",
reasoning: false,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: DEFAULT_CONTEXT_WINDOW,
maxTokens: DEFAULT_MAX_TOKENS,
};
}
async function testGatewayConnection(gatewayUrl: string): Promise<boolean> {
try {
const response = await fetch(`${gatewayUrl}/v1/models`, {
method: "GET",
headers: { "Content-Type": "application/json" },
signal: AbortSignal.timeout(5000),
});
return response.ok || response.status === 401; // 401 means gateway is up but needs auth
} catch {
return false;
}
}
async function fetchAvailableModels(consoleUrl: string): Promise<string[]> {
try {
// Try to get models from Higress Console API
const response = await fetch(`${consoleUrl}/v1/ai/routes`, {
method: "GET",
headers: { "Content-Type": "application/json" },
signal: AbortSignal.timeout(5000),
});
if (response.ok) {
const data = (await response.json()) as { data?: { model?: string }[] };
if (data.data && Array.isArray(data.data)) {
return data.data
.map((route: { model?: string }) => route.model)
.filter((m): m is string => typeof m === "string");
}
}
} catch {
// Ignore errors, use defaults
}
return [];
}
const higressPlugin = {
id: "higress-ai-gateway",
name: "Higress AI Gateway",
description: "Model provider plugin for Higress AI Gateway with auto-routing support",
configSchema: emptyPluginConfigSchema(),
register(api) {
api.registerProvider({
id: "higress",
label: "Higress AI Gateway",
docsPath: "/providers/models",
aliases: ["higress-gateway", "higress-ai"],
auth: [
{
id: "api-key",
label: "API Key",
hint: "Configure Higress AI Gateway endpoint with optional API key",
kind: "custom",
run: async (ctx) => {
// Step 1: Get Gateway URL
const gatewayUrlInput = await ctx.prompter.text({
message: "Higress AI Gateway URL",
initialValue: DEFAULT_GATEWAY_URL,
validate: validateUrl,
});
const gatewayUrl = normalizeBaseUrl(gatewayUrlInput);
// Step 2: Get Console URL (for auto-router configuration)
const consoleUrlInput = await ctx.prompter.text({
message: "Higress Console URL (for auto-router config)",
initialValue: DEFAULT_CONSOLE_URL,
validate: validateUrl,
});
const consoleUrl = normalizeBaseUrl(consoleUrlInput);
// Step 3: Test connection (create a new spinner)
const spin = ctx.prompter.progress("Testing gateway connection…");
const isConnected = await testGatewayConnection(gatewayUrl);
if (!isConnected) {
spin.stop("Gateway connection failed");
await ctx.prompter.note(
[
"Could not connect to Higress AI Gateway.",
"Make sure the gateway is running and the URL is correct.",
"",
`Tried: ${gatewayUrl}/v1/models`,
].join("\n"),
"Connection Warning",
);
} else {
spin.stop("Gateway connected");
}
// Step 4: Get API Key (optional for local gateway)
const apiKeyInput = await ctx.prompter.text({
message: "API Key (leave empty if not required)",
initialValue: "",
}) || '';
const apiKey = apiKeyInput.trim() || "higress-local";
// Step 5: Fetch available models (create a new spinner)
const spin2 = ctx.prompter.progress("Fetching available models…");
const fetchedModels = await fetchAvailableModels(consoleUrl);
const defaultModels = fetchedModels.length > 0
? ["higress/auto", ...fetchedModels]
: DEFAULT_MODEL_IDS;
spin2.stop();
// Step 6: Let user customize model list
const modelInput = await ctx.prompter.text({
message: "Model IDs (comma-separated, higress/auto enables auto-routing)",
initialValue: defaultModels.slice(0, 10).join(", "),
validate: (value) =>
parseModelIds(value).length > 0 ? undefined : "Enter at least one model id",
});
const modelIds = parseModelIds(modelInput);
const hasAutoModel = modelIds.includes("higress/auto");
// FIX: Avoid double prefix - if modelId already starts with provider, don't add prefix again
const defaultModelId = hasAutoModel
? "higress/auto"
: (modelIds[0] ?? "qwen-turbo");
const defaultModelRef = defaultModelId.startsWith("higress/")
? defaultModelId
: `higress/${defaultModelId}`;
// Step 7: Configure default model for auto-routing
let autoRoutingDefaultModel = "qwen-turbo";
if (hasAutoModel) {
const autoRoutingModelInput = await ctx.prompter.text({
message: "Default model for auto-routing (when no rule matches)",
initialValue: "qwen-turbo",
});
autoRoutingDefaultModel = autoRoutingModelInput.trim(); // FIX: Add trim() here
}
return {
profiles: [
{
profileId: `higress:${apiKey === "higress-local" ? "local" : "default"}`,
credential: {
type: "token",
provider: "higress",
token: apiKey,
},
},
],
configPatch: {
models: {
providers: {
higress: {
baseUrl: `${gatewayUrl}/v1`,
apiKey: apiKey,
api: "openai-completions",
authHeader: apiKey !== "higress-local",
models: modelIds.map((modelId) => buildModelDefinition(modelId)),
},
},
},
agents: {
defaults: {
models: Object.fromEntries(
modelIds.map((modelId) => {
// FIX: Avoid double prefix - only add provider prefix if not already present
const modelRef = modelId.startsWith("higress/")
? modelId
: `higress/${modelId}`;
return [modelRef, {}];
}),
),
},
},
plugins: {
entries: {
"higress-ai-gateway": {
enabled: true,
config: {
gatewayUrl,
consoleUrl,
autoRoutingDefaultModel,
},
},
},
},
},
defaultModel: defaultModelRef,
notes: [
"Higress AI Gateway is now configured as a model provider.",
hasAutoModel
? `Auto-routing enabled: use model "higress/auto" to route based on message content.`
: "Add 'higress/auto' to models to enable auto-routing.",
`Gateway endpoint: ${gatewayUrl}/v1/chat/completions`,
`Console: ${consoleUrl}`,
"",
"🎯 Recommended Skills (install via Clawdbot conversation):",
"",
"1. Auto-Routing Skill:",
" Configure automatic model routing based on message content",
" https://github.com/alibaba/higress/tree/main/.claude/skills/higress-auto-router",
' Say: "Install higress-auto-router skill"',
"",
"2. Agent Session Monitor Skill:",
" Track token usage and monitor conversation history",
" https://github.com/alibaba/higress/tree/main/.claude/skills/agent-session-monitor",
' Say: "Install agent-session-monitor skill"',
],
};
},
},
],
});
},
};
export default higressPlugin;

View File

@@ -1,22 +0,0 @@
{
"name": "@higress/higress-ai-gateway",
"version": "1.0.0",
"description": "Higress AI Gateway model provider plugin for Clawdbot with auto-routing support",
"main": "index.ts",
"clawdbot": {
"extensions": ["./index.ts"]
},
"keywords": [
"clawdbot",
"higress",
"ai-gateway",
"model-router",
"auto-routing"
],
"author": "Higress Team",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/alibaba/higress"
}
}

View File

@@ -1,92 +0,0 @@
# Higress AI Gateway Plugin
OpenClaw/Clawdbot model provider plugin for Higress AI Gateway with auto-routing support.
## What is this?
This is a TypeScript-based provider plugin that enables Clawdbot and OpenClaw to use Higress AI Gateway as a model provider. It provides:
- **Auto-routing support**: Use `higress/auto` to intelligently route requests based on message content
- **Dynamic model discovery**: Auto-detect available models from Higress Console
- **Smart URL handling**: Automatic URL normalization and validation
- **Flexible authentication**: Support for both local and remote gateway deployments
## Files
- **index.ts**: Main plugin implementation
- **package.json**: NPM package metadata and OpenClaw extension declaration
- **openclaw.plugin.json**: Plugin manifest for OpenClaw
## Installation
This plugin is automatically installed when you use the `higress-clawdbot-integration` skill. See the parent SKILL.md for complete installation instructions.
### Manual Installation
If you need to install manually:
```bash
# Detect runtime
if command -v clawdbot &> /dev/null; then
RUNTIME_DIR="$HOME/.clawdbot"
elif command -v openclaw &> /dev/null; then
RUNTIME_DIR="$HOME/.openclaw"
else
echo "Error: Neither clawdbot nor openclaw is installed"
exit 1
fi
# Copy plugin files
mkdir -p "$RUNTIME_DIR/extensions/higress-ai-gateway"
cp -r ./* "$RUNTIME_DIR/extensions/higress-ai-gateway/"
# Configure provider
clawdbot models auth login --provider higress
# or
openclaw models auth login --provider higress
```
## Usage
After installation, configure Higress as a model provider:
```bash
clawdbot models auth login --provider higress
```
The plugin will prompt for:
1. Gateway URL (default: http://localhost:8080)
2. Console URL (default: http://localhost:8001)
3. API Key (optional for local deployments)
4. Model list (auto-detected or manually specified)
5. Auto-routing default model (if using higress/auto)
## Auto-routing
To use auto-routing, include `higress/auto` in your model list during configuration. Then use it in your conversations:
```bash
# Use auto-routing
clawdbot chat --model higress/auto "深入思考 这个问题应该怎么解决?"
# The gateway will automatically route to the appropriate model based on:
# - Message content triggers (configured via higress-auto-router skill)
# - Fallback to default model if no rule matches
```
## Related Resources
- **Parent Skill**: [higress-clawdbot-integration](../SKILL.md)
- **Auto-routing Configuration**: [higress-auto-router](../../higress-auto-router/SKILL.md)
- **Session Monitoring**: [agent-session-monitor](../../agent-session-monitor/SKILL.md)
- **Higress AI Gateway**: https://github.com/higress-group/higress-standalone
## Compatibility
- **OpenClaw**: v2.0.0+
- **Clawdbot**: v2.0.0+
- **Higress AI Gateway**: All versions
## License
Apache-2.0

View File

@@ -0,0 +1,259 @@
---
name: higress-openclaw-integration
description: "Deploy and configure Higress AI Gateway for OpenClaw integration. Use when: (1) User wants to deploy Higress AI Gateway, (2) User wants to configure OpenClaw to use more model providers, (3) User mentions 'higress', 'ai gateway', 'model gateway', 'AI网关', (4) User wants to set up model routing or auto-routing, (5) User needs to manage LLM provider API keys."
---
# Higress AI Gateway Integration
Deploy Higress AI Gateway and configure OpenClaw to use it as a unified model provider.
## Quick Start
### Step 1: Collect Information from User
**Ask the user for the following information upfront:**
1. **Which LLM provider(s) to use?** (at least one required)
**Commonly Used Providers:**
| Provider | Parameter | Notes |
|----------|-----------|-------|
| 智谱 / z.ai | `--zhipuai-key` | Models: glm-*, Code Plan mode enabled by default |
| Claude Code | `--claude-code-key` | **Requires OAuth token from `claude setup-token`** |
| Moonshot (Kimi) | `--moonshot-key` | Models: moonshot-*, kimi-* |
| Minimax | `--minimax-key` | Models: abab-* |
| 阿里云通义千问 (Dashscope) | `--dashscope-key` | Models: qwen* |
| OpenAI | `--openai-key` | Models: gpt-*, o1-*, o3-* |
| DeepSeek | `--deepseek-key` | Models: deepseep-* |
| Grok | `--grok-key` | Models: grok-* |
**Other Providers:**
| Provider | Parameter | Notes |
|----------|-----------|-------|
| Claude | `--claude-key` | Models: claude-* |
| Google Gemini | `--gemini-key` | Models: gemini-* |
| OpenRouter | `--openrouter-key` | Supports all models (catch-all) |
| Groq | `--groq-key` | Fast inference |
| Doubao (豆包) | `--doubao-key` | Models: doubao-* |
| Mistral | `--mistral-key` | Models: mistral-* |
| Baichuan (百川) | `--baichuan-key` | Models: Baichuan* |
| 01.AI (Yi) | `--yi-key` | Models: yi-* |
| Stepfun (阶跃星辰) | `--stepfun-key` | Models: step-* |
| Cohere | `--cohere-key` | Models: command* |
| Fireworks AI | `--fireworks-key` | - |
| Together AI | `--togetherai-key` | - |
| GitHub Models | `--github-key` | - |
**Cloud Providers (require additional config):**
- Azure OpenAI: `--azure-key` (requires service URL)
- AWS Bedrock: `--bedrock-key` (requires region and access key)
- Google Vertex AI: `--vertex-key` (requires project ID and region)
**Brand Name Display (z.ai / 智谱):**
- If user communicates in Chinese: display as "智谱"
- If user communicates in English: display as "z.ai"
2. **Enable auto-routing?** (recommended)
- If yes: `--auto-routing --auto-routing-default-model <model-name>`
- Auto-routing allows using `model="higress/auto"` to automatically route requests based on message content
3. **Custom ports?** (optional, defaults: HTTP=8080, HTTPS=8443, Console=8001)
### Step 2: Deploy Gateway
**Auto-detect region for z.ai / 智谱 domain configuration:**
When user selects z.ai / 智谱 provider, detect their region:
```bash
# Run region detection script (scripts/detect-region.sh relative to skill directory)
REGION=$(bash scripts/detect-region.sh)
# Output: "china" or "international"
```
**Based on detection result:**
- If `REGION="china"`: use default domain `open.bigmodel.cn`, no extra parameter needed
- If `REGION="international"`: automatically add `--zhipuai-domain api.z.ai` to deployment command
**After deployment (for international users):**
Notify user in English: "The z.ai endpoint domain has been set to api.z.ai. If you want to change it, let me know and I can update the configuration."
```bash
# Create installation directory
mkdir -p higress-install
cd higress-install
# Download script (if not exists)
curl -fsSL https://higress.ai/ai-gateway/install.sh -o get-ai-gateway.sh
chmod +x get-ai-gateway.sh
# Deploy with user's configuration
# For z.ai / 智谱: always include --zhipuai-code-plan-mode
# For non-China users: include --zhipuai-domain api.z.ai
./get-ai-gateway.sh start --non-interactive \
--<provider>-key <api-key> \
[--auto-routing --auto-routing-default-model <model>]
```
**z.ai / 智谱 Options:**
| Option | Description |
|--------|-------------|
| `--zhipuai-code-plan-mode` | Enable Code Plan mode (enabled by default) |
| `--zhipuai-domain <domain>` | Custom domain, default: `open.bigmodel.cn` (China), `api.z.ai` (international) |
**Example (China user):**
```bash
./get-ai-gateway.sh start --non-interactive \
--zhipuai-key sk-xxx \
--zhipuai-code-plan-mode \
--auto-routing \
--auto-routing-default-model glm-5
```
**Example (International user):**
```bash
./get-ai-gateway.sh start --non-interactive \
--zhipuai-key sk-xxx \
--zhipuai-domain api.z.ai \
--zhipuai-code-plan-mode \
--auto-routing \
--auto-routing-default-model glm-5
```
### Step 3: Install OpenClaw Plugin
Install the Higress provider plugin for OpenClaw:
```bash
# Copy plugin files (PLUGIN_SRC is relative to skill directory: scripts/plugin)
PLUGIN_SRC="scripts/plugin"
PLUGIN_DEST="$HOME/.openclaw/extensions/higress"
mkdir -p "$PLUGIN_DEST"
cp -r "$PLUGIN_SRC"/* "$PLUGIN_DEST/"
```
**Tell user to run the following commands manually in their terminal (interactive commands, cannot be executed by AI agent):**
```bash
# Step 1: Enable the plugin
openclaw plugins enable higress
# Step 2: Configure provider (interactive - will prompt for Gateway URL, API Key, models, etc.)
openclaw models auth login --provider higress --set-default
# Step 3: Restart OpenClaw gateway to apply changes
openclaw gateway restart
```
The `openclaw models auth login` command will interactively prompt for:
1. Gateway URL (default: `http://localhost:8080`)
2. Console URL (default: `http://localhost:8001`)
3. API Key (optional for local deployments)
4. Model list (auto-detected or manually specified)
5. Auto-routing default model (if using `higress/auto`)
After configuration and restart, Higress models are available in OpenClaw with `higress/` prefix (e.g., `higress/glm-5`, `higress/auto`).
**Future Configuration Updates (No Restart Needed)**
After the initial setup, you can manage your configuration through conversation with OpenClaw:
- **Add New Providers**: Add new LLM providers (e.g., DeepSeek, OpenAI, Claude) and their models dynamically.
- **Update API Keys**: Update existing provider API keys without service restart.
- **Configure Auto-routing**: If you've set up multiple models, ask OpenClaw to configure auto-routing rules. Requests will be intelligently routed based on your message content, using the most suitable model automatically.
All configuration changes are hot-loaded through Higress — no `openclaw gateway restart` required. Iterate on your model provider setup dynamically without service interruption!
## Post-Deployment Management
### Add/Update API Keys (Hot-reload)
```bash
./get-ai-gateway.sh config add --provider <provider> --key <api-key>
./get-ai-gateway.sh config list
./get-ai-gateway.sh config remove --provider <provider>
```
Provider aliases: `dashscope`/`qwen`, `moonshot`/`kimi`, `zhipuai`/`zhipu`
### Update z.ai Domain (Hot-reload)
If user wants to change the z.ai domain after deployment:
```bash
# Update domain configuration
./get-ai-gateway.sh config add --provider zhipuai --extra-config "zhipuDomain=api.z.ai"
# Or revert to China endpoint
./get-ai-gateway.sh config add --provider zhipuai --extra-config "zhipuDomain=open.bigmodel.cn"
```
### Add Routing Rules (for auto-routing)
```bash
# Add rule: route to specific model when message starts with trigger
./get-ai-gateway.sh route add --model <model> --trigger "keyword1|keyword2"
# Examples
./get-ai-gateway.sh route add --model glm-4-flash --trigger "quick|fast"
./get-ai-gateway.sh route add --model claude-opus-4 --trigger "think|complex"
./get-ai-gateway.sh route add --model deepseek-coder --trigger "code|debug"
# List/remove rules
./get-ai-gateway.sh route list
./get-ai-gateway.sh route remove --rule-id 0
```
### Stop/Delete Gateway
```bash
./get-ai-gateway.sh stop
./get-ai-gateway.sh delete
```
## Endpoints
| Endpoint | URL |
|----------|-----|
| Chat Completions | http://localhost:8080/v1/chat/completions |
| Console | http://localhost:8001 |
| Logs | `./higress-install/logs/access.log` |
## Testing
```bash
# Test with specific model
curl 'http://localhost:8080/v1/chat/completions' \
-H 'Content-Type: application/json' \
-d '{"model": "<model-name>", "messages": [{"role": "user", "content": "Hello"}]}'
# Test auto-routing (if enabled)
curl 'http://localhost:8080/v1/chat/completions' \
-H 'Content-Type: application/json' \
-d '{"model": "higress/auto", "messages": [{"role": "user", "content": "What is AI?"}]}'
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Container fails to start | Check `docker logs higress-ai-gateway` |
| Port already in use | Use `--http-port`, `--console-port` to change ports |
| API key error | Run `./get-ai-gateway.sh config list` to verify keys |
| Auto-routing not working | Ensure `--auto-routing` was set during deployment |
| Slow image download | Script auto-selects nearest registry based on timezone |
## Important Notes
1. **Claude Code Mode**: Requires OAuth token from `claude setup-token` command, not a regular API key
2. **z.ai Code Plan Mode**: Enabled by default, uses `/api/coding/paas/v4/chat/completions` endpoint, optimized for coding tasks
3. **z.ai Domain Selection**:
- China users: `open.bigmodel.cn` (default)
- International users: `api.z.ai` (auto-detected based on timezone)
- Users can update domain anytime after deployment
4. **Auto-routing**: Must be enabled during initial deployment (`--auto-routing`); routing rules can be added later
5. **OpenClaw Integration**: The `openclaw models auth login` and `openclaw gateway restart` commands are **interactive** and must be run by the user manually in their terminal
6. **Hot-reload**: API key changes take effect immediately; no container restart needed

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Detect if user is in China region based on timezone
# Returns: "china" or "international"
TIMEZONE=$(cat /etc/timezone 2>/dev/null || timedatectl show --property=Timezone --value 2>/dev/null || echo "Unknown")
# Check if timezone indicates China region (including Hong Kong)
if [[ "$TIMEZONE" == "Asia/Shanghai" ]] || \
[[ "$TIMEZONE" == "Asia/Hong_Kong" ]] || \
[[ "$TIMEZONE" == *"China"* ]] || \
[[ "$TIMEZONE" == *"Beijing"* ]]; then
echo "china"
else
echo "international"
fi

View File

@@ -0,0 +1,61 @@
# Higress AI Gateway Plugin
OpenClaw model provider plugin for Higress AI Gateway with auto-routing support.
## What is this?
This is a TypeScript-based provider plugin that enables OpenClaw to use Higress AI Gateway as a model provider. It provides:
- **Auto-routing support**: Use `higress/auto` to intelligently route requests based on message content
- **Dynamic model discovery**: Auto-detect available models from Higress Console
- **Smart URL handling**: Automatic URL normalization and validation
- **Flexible authentication**: Support for both local and remote gateway deployments
## Files
- **index.ts**: Main plugin implementation
- **package.json**: NPM package metadata and OpenClaw extension declaration
- **openclaw.plugin.json**: Plugin manifest for OpenClaw
## Installation
This plugin is automatically installed when you use the `higress-openclaw-integration` skill. See parent SKILL.md for complete installation instructions.
### Manual Installation
If you need to install manually:
```bash
# Copy plugin files
mkdir -p "$HOME/.openclaw/extensions/higress"
cp -r ./* "$HOME/.openclaw/extensions/higress/"
# Configure provider
openclaw plugins enable higress
openclaw models auth login --provider higress
```
## Usage
After installation, configure Higress as a model provider:
```bash
openclaw models auth login --provider higress
```
The plugin will prompt for:
1. Gateway URL (default: http://localhost:8080)
2. Console URL (default: http://localhost:8001)
3. API Key (optional for local deployments)
4. Model list (auto-detected or manually specified)
5. Auto-routing default model (if using higress/auto)
## Related Resources
- **Parent Skill**: [higress-openclaw-integration](../SKILL.md)
- **Auto-routing Configuration**: [higress-auto-router](../../higress-auto-router/SKILL.md)
## License
Apache-2.0

View File

@@ -2,33 +2,47 @@ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
const DEFAULT_GATEWAY_URL = "http://localhost:8080";
const DEFAULT_CONSOLE_URL = "http://localhost:8001";
const DEFAULT_CONTEXT_WINDOW = 128_000;
const DEFAULT_MAX_TOKENS = 8192;
// Model-specific context window and max tokens configurations
const MODEL_CONFIG: Record<string, { contextWindow: number; maxTokens: number }> = {
"gpt-5.4": { contextWindow: 1_000_000, maxTokens: 128_000 },
"gpt-5.4-mini": { contextWindow: 400_000, maxTokens: 128_000 },
"gpt-5.4-nano": { contextWindow: 400_000, maxTokens: 128_000 },
"claude-opus-4-6": { contextWindow: 1_000_000, maxTokens: 128_000 },
"claude-sonnet-4-6": { contextWindow: 1_000_000, maxTokens: 64_000 },
"claude-haiku-4-5": { contextWindow: 200_000, maxTokens: 64_000 },
"qwen3.5-plus": { contextWindow: 960_000, maxTokens: 64_000 },
"deepseek-chat": { contextWindow: 256_000, maxTokens: 128_000 },
"deepseek-reasoner": { contextWindow: 256_000, maxTokens: 128_000 },
"kimi-k2.5": { contextWindow: 256_000, maxTokens: 128_000 },
"glm-5": { contextWindow: 200_000, maxTokens: 128_000 },
"MiniMax-M2.5": { contextWindow: 200_000, maxTokens: 128_000 },
};
// Default values for unknown models
const DEFAULT_CONTEXT_WINDOW = 200_000;
const DEFAULT_MAX_TOKENS = 128_000;
// Common models that Higress AI Gateway typically supports
const DEFAULT_MODEL_IDS = [
// Auto-routing special model
"higress/auto",
// OpenAI models
"gpt-5.2",
"gpt-5-mini",
"gpt-5-nano",
// Commonly models
"kimi-k2.5",
"glm-5",
"MiniMax-M2.5",
"qwen3.5-plus",
// Anthropic models
"claude-opus-4.5",
"claude-sonnet-4.5",
"claude-haiku-4.5",
// Qwen models
"qwen3-turbo",
"qwen3-plus",
"qwen3-max",
"qwen3-coder-480b-a35b-instruct",
"claude-opus-4-6",
"claude-sonnet-4-6",
"claude-haiku-4-5",
// OpenAI models
"gpt-5.4",
"gpt-5.4-mini",
"gpt-5.4-nano",
// DeepSeek models
"deepseek-chat",
"deepseek-reasoner",
// Other common models
"kimi-k2.5",
"glm-4.7",
"MiniMax-M2.1",
"deepseek-reasoner",
] as const;
function normalizeBaseUrl(value: string): string {
@@ -60,26 +74,33 @@ function parseModelIds(input: string): string[] {
function buildModelDefinition(modelId: string) {
const isAutoModel = modelId === "higress/auto";
const config = MODEL_CONFIG[modelId] || { contextWindow: DEFAULT_CONTEXT_WINDOW, maxTokens: DEFAULT_MAX_TOKENS };
return {
id: modelId,
name: isAutoModel ? "Higress Auto Router" : modelId,
api: "openai-completions",
reasoning: false,
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: DEFAULT_CONTEXT_WINDOW,
maxTokens: DEFAULT_MAX_TOKENS,
contextWindow: config.contextWindow,
maxTokens: config.maxTokens,
};
}
async function testGatewayConnection(gatewayUrl: string): Promise<boolean> {
try {
const response = await fetch(`${gatewayUrl}/v1/models`, {
method: "GET",
// gatewayUrl already ends with /v1 from normalizeBaseUrl()
// Use chat/completions endpoint with empty body to test connection
// Higress doesn't support /models endpoint
const response = await fetch(`${gatewayUrl}/chat/completions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
signal: AbortSignal.timeout(5000),
});
return response.ok || response.status === 401; // 401 means gateway is up but needs auth
// Any response (including 400/401/422) means gateway is reachable
return true;
} catch {
return false;
}
@@ -108,7 +129,7 @@ async function fetchAvailableModels(consoleUrl: string): Promise<string[]> {
}
const higressPlugin = {
id: "higress-ai-gateway",
id: "higress",
name: "Higress AI Gateway",
description: "Model provider plugin for Higress AI Gateway with auto-routing support",
configSchema: emptyPluginConfigSchema(),
@@ -150,8 +171,6 @@ const higressPlugin = {
[
"Could not connect to Higress AI Gateway.",
"Make sure the gateway is running and the URL is correct.",
"",
`Tried: ${gatewayUrl}/v1/models`,
].join("\n"),
"Connection Warning",
);
@@ -184,21 +203,19 @@ const higressPlugin = {
const modelIds = parseModelIds(modelInput);
const hasAutoModel = modelIds.includes("higress/auto");
// FIX: Avoid double prefix - if modelId already starts with provider, don't add prefix again
const defaultModelId = hasAutoModel
? "higress/auto"
: (modelIds[0] ?? "qwen-turbo");
const defaultModelRef = defaultModelId.startsWith("higress/")
? defaultModelId
: `higress/${defaultModelId}`;
// Always add higress/ provider prefix to create model reference
const defaultModelId = hasAutoModel
? "higress/auto"
: (modelIds[0] ?? "glm-5");
const defaultModelRef = `higress/${defaultModelId}`;
// Step 7: Configure default model for auto-routing
let autoRoutingDefaultModel = "qwen-turbo";
let autoRoutingDefaultModel = "glm-5";
if (hasAutoModel) {
const autoRoutingModelInput = await ctx.prompter.text({
message: "Default model for auto-routing (when no rule matches)",
initialValue: "qwen-turbo",
initialValue: "glm-5",
});
autoRoutingDefaultModel = autoRoutingModelInput.trim(); // FIX: Add trim() here
}
@@ -218,7 +235,8 @@ const higressPlugin = {
models: {
providers: {
higress: {
baseUrl: `${gatewayUrl}/v1`,
// gatewayUrl already ends with /v1 from normalizeBaseUrl()
baseUrl: gatewayUrl,
apiKey: apiKey,
api: "openai-completions",
authHeader: apiKey !== "higress-local",
@@ -230,10 +248,8 @@ const higressPlugin = {
defaults: {
models: Object.fromEntries(
modelIds.map((modelId) => {
// FIX: Avoid double prefix - only add provider prefix if not already present
const modelRef = modelId.startsWith("higress/")
? modelId
: `higress/${modelId}`;
// Always add higress/ provider prefix to create model reference
const modelRef = `higress/${modelId}`;
return [modelRef, {}];
}),
),
@@ -241,7 +257,7 @@ const higressPlugin = {
},
plugins: {
entries: {
"higress-ai-gateway": {
"higress": {
enabled: true,
config: {
gatewayUrl,
@@ -258,20 +274,22 @@ const higressPlugin = {
hasAutoModel
? `Auto-routing enabled: use model "higress/auto" to route based on message content.`
: "Add 'higress/auto' to models to enable auto-routing.",
`Gateway endpoint: ${gatewayUrl}/v1/chat/completions`,
// gatewayUrl already ends with /v1 from normalizeBaseUrl()
`Gateway endpoint: ${gatewayUrl}/chat/completions`,
`Console: ${consoleUrl}`,
"",
"🎯 Recommended Skills (install via Clawdbot conversation):",
"💡 Future Configuration Updates (No Restart Needed):",
" • Add New Providers: Add LLM providers (DeepSeek, OpenAI, Claude, etc.) dynamically.",
" • Update API Keys: Update existing provider keys without restart.",
" • Configure Auto-Routing: Ask OpenClaw to set up intelligent routing rules.",
" All changes hot-load via Higress — no gateway restart required!",
"",
"🎯 Recommended Skills (install via OpenClaw conversation):",
"",
"1. Auto-Routing Skill:",
" Configure automatic model routing based on message content",
" https://github.com/alibaba/higress/tree/main/.claude/skills/higress-auto-router",
' Say: "Install higress-auto-router skill"',
"",
"2. Agent Session Monitor Skill:",
" Track token usage and monitor conversation history",
" https://github.com/alibaba/higress/tree/main/.claude/skills/agent-session-monitor",
' Say: "Install agent-session-monitor skill"',
],
};
},

View File

@@ -1,5 +1,5 @@
{
"id": "higress-ai-gateway",
"id": "higress",
"name": "Higress AI Gateway",
"description": "Model provider plugin for Higress AI Gateway with auto-routing support",
"providers": ["higress"],

View File

@@ -1,5 +1,5 @@
{
"name": "@higress/higress-ai-gateway",
"name": "@higress/higress",
"version": "1.0.0",
"description": "Higress AI Gateway model provider plugin for OpenClaw with auto-routing support",
"main": "index.ts",

View File

@@ -0,0 +1,35 @@
---
name: issue-spec-apply
description: Implement PROCESS comments for an issue-spec change and keep PR traceability synchronized.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Apply
Use when the user asks for /issue-spec:apply, issue-spec apply, or implementing PROCESS/TASK scopes from an issue-spec change.
## Steps
1. Read proposal/design/implement issue context and list typed comments with issue-spec comment list --json.
2. Confirm issue-spec auth status --json includes the expected GitHub backend. Local gh-authenticated sessions can use the native gh backend; keep ISSUE_SPEC_TOKEN="$(gh auth token)" only as an older-version or forced-rest compatibility path.
3. Create or update PROCESS comments with owner agent, scope, dependencies, write ownership, and status.
4. Split non-trivial work into independent worker PROCESS nodes when file/module ownership does not overlap; execute independent workers in parallel when available.
5. Add dedicated review PROCESS nodes for non-trivial changes. Review PROCESS nodes should own review scopes such as CLI/API behavior, workflow docs, tests, compatibility, or security-sensitive surfaces.
6. Link each PROCESS to its TASK comments with issue-spec link.
7. Implement the code changes for one PROCESS scope at a time, or integrate completed worker outputs by dependency order.
8. Link every worker and review PROCESS to the PR with issue-spec pr link-process.
9. Add PR rationale comments on key changed lines with issue-spec pr rationale, each linked to a SPEC comment.
10. Mark PROCESS comments done only after implementation/review work and focused verification evidence exist.
## Coordinator DAG Execution
1. Build the ready set from PROCESS nodes whose dependencies are done.
2. Keep immediate blocking work local when the next step depends on it.
3. Spawn or assign independent worker agents only when their write ownership is disjoint.
4. Spawn or assign independent review agents only when their review scopes are disjoint.
5. Integrate completed outputs by dependency order and update PROCESS evidence before marking done.

View File

@@ -0,0 +1,24 @@
---
name: issue-spec-archive
description: Create the post-merge durable spec archive PR for an issue-spec change.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Archive
Use when the user asks for /issue-spec:archive, issue-spec archive, or creating the post-merge durable spec PR.
## Steps
1. Confirm the implementation PR is merged.
2. Create the durable spec PR:
issue-spec archive durable-spec --repo higress-group/higress --proposal <issue> --capability <capability> --create-pr --branch issue-spec/durable-spec-<capability> --json
3. Review the durable spec PR for long-lived behavior only. Do not copy process records, review findings, or verification logs into durable specs.
4. After durable spec PR merge, keep proposal/design/implement issues as audit history unless the project policy says to close them.

View File

@@ -0,0 +1,58 @@
---
name: issue-spec-github
description: Use GitHub CLI for GitHub issues, pull requests, CI runs, and API queries that issue-spec does not wrap.
license: MIT
compatibility: Requires GitHub CLI (gh).
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# GitHub CLI
Use the `gh` CLI to interact with GitHub repositories, issues, pull requests, CI, and API endpoints.
## When To Use
- Checking PR status, reviews, mergeability, or CI checks.
- Creating, viewing, updating, closing, or commenting on GitHub issues.
- Listing or inspecting pull requests, workflow runs, releases, labels, or repository metadata.
- Calling GitHub API endpoints with `gh api` when issue-spec does not provide a dedicated command.
## When Not To Use
- Local git operations such as commit, branch, fetch, merge, or push. Use `git` directly.
- Non-GitHub repositories. Use the matching provider CLI instead.
- Complex code review across local diffs. Read the repository files directly and use issue-spec review commands for traceable findings.
## Setup
```bash
gh auth login
gh auth status
```
## Common Commands
```bash
gh issue list --repo owner/repo --state open
gh issue view 42 --repo owner/repo --json number,title,state,url,body
gh issue comment 42 --repo owner/repo --body "Comment body"
gh pr list --repo owner/repo
gh pr view 17 --repo owner/repo --json number,title,state,headRefName,baseRefName,url
gh pr checks 17 --repo owner/repo
gh run list --repo owner/repo --limit 10
gh run view <run-id> --repo owner/repo --log-failed
gh api repos/owner/repo/labels --jq '.[].name'
```
## Notes
- Always pass `--repo owner/repo` when the current directory is not definitely inside the target repository.
- Use GitHub URLs directly when convenient, for example `gh pr view https://github.com/owner/repo/pull/17`.
- Prefer structured output with `--json` and `--jq` when another command or agent step consumes the result.
- issue-spec owns the proposal, design, implement, typed comment, review, verify, and archive workflow state. Use `gh` for adjacent GitHub operations that are outside issue-spec's command surface.

View File

@@ -0,0 +1,37 @@
---
name: issue-spec-propose
description: Create or continue proposal, SPEC, QUESTION, design, and TASK artifacts for an issue-spec change.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Propose
Use when the user asks for /issue-spec:propose, issue-spec propose, creating a change proposal, drafting SPEC comments, or preparing design/tasks after questions converge.
## Steps
1. Create the proposal issue:
issue-spec issue create proposal --repo higress-group/higress --change <change-name> --body-file <proposal.md>
2. If the proposal body needs revision after discussion, update it in place:
issue-spec issue update --repo higress-group/higress --issue <proposal-issue> --body-file <proposal.md> --summary "<what changed>"
3. Add SPEC comments with issue-spec comment upsert --type SPEC. SPEC comments must use MUST/SHALL and WHEN/THEN scenarios.
4. Add QUESTION comments for unresolved behavior with issue-spec question create and resolve blocking questions before design.
5. Create the design issue after SPEC/QUESTION convergence:
issue-spec issue create design --repo higress-group/higress --change <change-name> --proposal <proposal-issue-or-url> --body-file <design.md>
6. Add TASK comments with issue-spec comment upsert --type TASK and link every TASK to covered SPEC comments with issue-spec link.
7. Create the implement issue once tasks are ready:
issue-spec issue create implement --repo higress-group/higress --change <change-name> --proposal <proposal-issue-or-url> --design <design-issue-or-url> --body-file <implement.md>
8. Run issue-spec verify-links and fix missing backlinks before implementation.

View File

@@ -0,0 +1,32 @@
---
name: issue-spec-review
description: Review an issue-spec implementation PR, create PR line findings, reply after fixes, and sync REVIEW comments.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Review
Use when the user asks for /issue-spec:review, issue-spec review, or a PR review gate for an issue-spec implementation.
## Steps
1. Run issue-spec review sync --repo higress-group/higress --pr <number> --implement <issue> --id REVIEW-<n> --json to capture current rationale comments, findings, and checks.
2. For non-trivial PRs, spawn or assign dedicated review agents as review PROCESS owners. Multiple review agents can run in parallel when their review scopes are independent.
3. Give each review agent a concrete scope and expected output: actionable findings only, severity, file/line, linked SPEC, owner PROCESS, and suggested fix.
4. Create actionable PR line findings with issue-spec review finding. Use P0/P1 for blockers and P2 for non-blocking follow-up.
5. Assign every finding to a PROCESS owner. If no findings are found, record that result in REVIEW or VERIFY evidence.
6. After the worker fixes a finding, reply to the original thread with issue-spec review reply --status resolved.
7. Re-run review sync. P0/P1 findings must be resolved before final verify/archive.
## Review DAG Policy
1. Every non-trivial PR should have at least one dedicated review PROCESS node before final verify.
2. Use multiple review agents in parallel when scopes are independent, for example CLI/API behavior, workflow docs, tests, compatibility, or security-sensitive surfaces.
3. A review agent reports findings only; the coordinator converts actionable line findings into issue-spec review finding comments.
4. P0/P1 findings block final verify until the owner PROCESS fixes them and issue-spec review reply records the resolution on the original thread.
5. If a review agent finds no issues, record that result in REVIEW or VERIFY evidence before marking the review PROCESS done.

View File

@@ -0,0 +1,28 @@
---
name: issue-spec-verify
description: Run final issue-spec verification across traceability, questions, review findings, PR rationale, PR checks, and durable spec draft.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Verify
Use when the user asks for /issue-spec:verify, issue-spec verify, or final readiness evidence before merge/archive.
## Steps
1. Run focused project tests and record evidence in VERIFY comments.
2. Run issue-spec verify-links --repo higress-group/higress --proposal <issue> --design <issue> --implement <issue> --json.
3. Render a durable spec draft:
issue-spec archive durable-spec --repo higress-group/higress --proposal <issue> --capability <capability> --output /tmp/<capability>-spec.md --json
4. Run final verify:
issue-spec verify --repo higress-group/higress --proposal <issue> --design <issue> --implement <issue> --pr <pr> --durable-spec /tmp/<capability>-spec.md --json
5. Final verify must fail if blocking questions, missing links, missing PROCESS rationale, open P0/P1 findings, failed or pending PR checks, or durable spec omissions exist.

View File

@@ -0,0 +1,53 @@
---
name: issue-spec-workflow
description: Use issue-spec to run an issue-native OpenSpec-style workflow with GitHub issues, typed comments, PR review comments, final verification, and durable spec archive PRs.
license: MIT
compatibility: Requires issue-spec CLI.
metadata:
author: issue-spec
version: "1.0"
generatedBy: "issue-spec"
---
# Issue Spec Workflow
Use this skill for issue-native OpenSpec work. Active change artifacts live in GitHub issues and issue comments; durable specs are repository files created after implementation merge.
## Start
1. Run issue-spec auth status --json and confirm the active auth source and GitHub backend.
2. Run issue-spec status --repo higress-group/higress --proposal <issue> --design <issue> --implement <issue> --json when issues already exist.
3. For new work, create proposal, design, and implement issues with issue-spec issue create and pass --body-file with concrete markdown content.
4. When an issue body changes, update it in place with issue-spec issue update --body-file and include --summary for the human-readable audit trail.
5. Store requirements, tasks, process ownership, review, and verify evidence as typed comments.
## GitHub Backend
- Local agents may rely on native GitHub CLI support: when no ISSUE_SPEC_TOKEN, GH_TOKEN, GITHUB_TOKEN, keyring token, or issue-spec config token is present and gh auth status --active succeeds for the target host, issue-spec auto-selects the gh backend.
- Explicit env or stored issue-spec tokens keep the rest backend under auto selection. Set ISSUE_SPEC_GITHUB_BACKEND=rest or ISSUE_SPEC_GITHUB_BACKEND=gh only when a workflow needs deterministic backend selection.
- The gh backend proxies GitHub API operations through gh api and uses gh --hostname for Enterprise hosts. It does not replace local git commands.
- ISSUE_SPEC_API_URL applies to the rest backend. Forced gh mode should be used only with hosts that gh can address.
- Use ISSUE_SPEC_TOKEN="$(gh auth token)" only for older issue-spec versions or when deliberately forcing rest while sourcing the token from gh.
## Rules
- Create SPEC comments before design; each SPEC must be testable and include WHEN/THEN scenarios.
- Do not leave active proposal/design/implement issue bodies as TBD placeholders.
- Resolve blocking QUESTION comments before design/tasks, or explicitly record accepted assumptions.
- Link SPEC <-> TASK and TASK <-> PROCESS with issue-spec link.
- Link every PROCESS to the implementation PR with issue-spec pr link-process.
- For non-trivial changes, include review PROCESS nodes in the DAG; review agents are scheduled like worker agents and can run in parallel when their review scopes are independent.
- Small changes may stay coordinator-only, but record the serial execution decision in the implement or VERIFY evidence.
- Before human review, add PR rationale comments with issue-spec pr rationale for every active PROCESS.
- Use issue-spec review finding for PR line findings and issue-spec review reply to close the original thread.
- Run issue-spec review sync and issue-spec verify before declaring ready.
- After the implementation PR merges, create the separate durable spec PR with issue-spec archive durable-spec --create-pr.
## Coordinator DAG Execution
1. Treat PROCESS comments as DAG nodes with explicit owner, dependencies, write or review scope, PR link, and evidence.
2. Select ready PROCESS nodes whose dependencies are done and whose scopes do not overlap.
3. Dispatch independent worker PROCESS nodes in parallel when their file/module ownership is disjoint.
4. Dispatch independent review PROCESS nodes in parallel for non-trivial PRs after PR rationale exists.
5. Integrate completed worker outputs by dependency order; route P0/P1 review findings back to the owner PROCESS.
6. Mark PROCESS nodes done only after their implementation or review evidence is recorded and blocking findings are resolved.

View File

@@ -0,0 +1,106 @@
name: Build Plugin Server Image and Push
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
plugin_server_ref:
description: "plugin-server repo ref (branch/tag/commit, default: main)"
required: false
default: "main"
type: string
version:
description: "Version tag (optional, without leading v)"
required: false
type: string
jobs:
build-plugin-server-image:
runs-on: ubuntu-latest
environment:
name: image-registry-plugin-server
env:
IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
IMAGE_NAME: ${{ vars.PLUGIN_SERVER_IMAGE_NAME || 'higress/plugin-server' }}
steps:
- name: "Clone plugin-server repository"
uses: actions/checkout@v4
with:
repository: higress-group/plugin-server
ref: ${{ github.event.inputs.plugin_server_ref || 'main' }}
path: plugin-server
fetch-depth: 1
- name: Free Up GitHub Actions Ubuntu Runner Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: true
dotnet: true
haskell: true
large-packages: true
swap-storage: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
image: tonistiigi/binfmt:qemu-v7.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-plugin-server-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-plugin-server-
- name: Determine version
id: version
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ github.event.inputs.version }}" ]]; then
echo "manual_version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
fi
- name: Calculate Docker metadata
id: docker-meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha
type=ref,event=tag
type=semver,pattern={{version}}
type=raw,value=${{ steps.version.outputs.manual_version }},enable=${{ steps.version.outputs.manual_version != '' }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: ${{ env.IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build Docker Image and Push
run: |
BUILT_IMAGE=""
readarray -t IMAGES <<< "${{ steps.docker-meta.outputs.tags }}"
for image in "${IMAGES[@]}"; do
echo "Image: $image"
if [ "$BUILT_IMAGE" == "" ]; then
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t "$image" \
-f plugin-server/Dockerfile \
--push \
plugin-server
BUILT_IMAGE="$image"
else
docker buildx imagetools create "$BUILT_IMAGE" --tag "$image"
fi
done

View File

@@ -19,7 +19,7 @@ on:
jobs:
lint:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
@@ -30,7 +30,7 @@ jobs:
# - run: make lint
higress-wasmplugin-test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
matrix:
# TODO(Xunzhuo): Enable C WASM Filters in CI
@@ -38,6 +38,18 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Disable containerd image store
run: |
sudo bash -c 'cat > /etc/docker/daemon.json << EOF
{
"features": {
"containerd-snapshotter": false
}
}
EOF'
sudo systemctl restart docker
docker info -f '{{ .DriverStatus }}'
- name: Free Up GitHub Actions Ubuntu Runner Disk Space 🔧
uses: jlumbroso/free-disk-space@main
with:
@@ -79,7 +91,7 @@ jobs:
command: GOPROXY="https://proxy.golang.org,direct" PLUGIN_TYPE=${{ matrix.wasmPluginType }} make higress-wasmplugin-test
publish:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: [higress-wasmplugin-test]
steps:
- uses: actions/checkout@v4

View File

@@ -10,7 +10,7 @@ env:
GO_VERSION: 1.24
jobs:
lint:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
@@ -21,7 +21,7 @@ jobs:
# - run: make lint
coverage-test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
@@ -57,7 +57,7 @@ jobs:
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: [lint, coverage-test]
steps:
- name: "Checkout ${{ github.ref }}"
@@ -91,17 +91,29 @@ jobs:
path: out/
gateway-conformance-test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: [build]
steps:
- uses: actions/checkout@v3
higress-conformance-test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: [build]
steps:
- uses: actions/checkout@v4
- name: Disable containerd image store
run: |
sudo bash -c 'cat > /etc/docker/daemon.json << EOF
{
"features": {
"containerd-snapshotter": false
}
}
EOF'
sudo systemctl restart docker
docker info -f '{{ .DriverStatus }}'
- name: Free Up GitHub Actions Ubuntu Runner Disk Space 🔧
uses: jlumbroso/free-disk-space@main
with:
@@ -139,7 +151,7 @@ jobs:
run: GOPROXY="https://proxy.golang.org,direct" make higress-conformance-test
publish:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: [higress-conformance-test, gateway-conformance-test]
steps:
- uses: actions/checkout@v4

View File

@@ -0,0 +1,50 @@
name: Sync Skills to OSS
on:
push:
branches:
- main
paths:
- '.claude/skills/**'
workflow_dispatch: ~
jobs:
sync-skills-to-oss:
runs-on: ubuntu-latest
environment:
name: oss
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download AI Gateway Install Script
run: |
wget -O install.sh https://raw.githubusercontent.com/higress-group/higress-standalone/main/all-in-one/get-ai-gateway.sh
chmod +x install.sh
- name: Package Skills
run: |
mkdir -p packaged-skills
for skill_dir in .claude/skills/*/; do
if [ -d "$skill_dir" ]; then
skill_name=$(basename "$skill_dir")
echo "Packaging $skill_name..."
(cd "$skill_dir" && zip -r "$GITHUB_WORKSPACE/packaged-skills/${skill_name}.zip" .)
fi
done
- name: Sync Skills to OSS
uses: go-choppy/ossutil-github-action@master
with:
ossArgs: 'cp -r -u packaged-skills/ oss://higress-ai/skills/'
accessKey: ${{ secrets.ACCESS_KEYID }}
accessSecret: ${{ secrets.ACCESS_KEYSECRET }}
endpoint: oss-cn-hongkong.aliyuncs.com
- name: Sync Install Script to OSS
uses: go-choppy/ossutil-github-action@master
with:
ossArgs: 'cp -u install.sh oss://higress-ai/ai-gateway/install.sh'
accessKey: ${{ secrets.ACCESS_KEYID }}
accessSecret: ${{ secrets.ACCESS_KEYSECRET }}
endpoint: oss-cn-hongkong.aliyuncs.com

41
.github/workflows/sync.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Sync Upstream
on:
schedule:
# 每天 UTC 时间 00:00 运行 (北京时间 08:00)
- cron: '0 0 * * *'
workflow_dispatch: # 允许手动触发
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0 # 获取所有历史记录,防止报错
- name: Setup Git
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Add Upstream Remote
# 将 <原始仓库URL> 替换为你要同步的原始项目地址
run: git remote add upstream https://github.com/alibaba/higress.git
- name: Fetch Upstream
run: git fetch upstream
- name: Merge Upstream Changes
# 将 upstream/main 替换为 upstream/master 或其他分支名
run: |
git checkout main
git merge upstream/main --no-edit
# 如果合并冲突,可以使用 git merge --abort 中止,或者手动处理
- name: Push to Origin
# 使用 secrets.GITHUB_TOKEN 自动推送,无需配置 PAT
run: git push origin main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -115,7 +115,8 @@ jobs:
run: |
echo "Building WASM for ${{ matrix.plugin }}..."
# 检查是否存在main.go文件
# Run prepare.sh if it exists (e.g., download BPE vocabulary)
if [ -f prepare.sh ]; then sh ./prepare.sh; fi
export GOOS=wasip1
export GOARCH=wasm
@@ -199,15 +200,14 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go 1.24
- name: Set up Go 1.25
uses: actions/setup-go@v4
with:
go-version: 1.24
go-version: 1.25
cache: true
- name: Install required tools
run: |
go install github.com/wadey/gocovmerge@latest
sudo apt-get update && sudo apt-get install -y bc
- name: Download all test results

4
.issue-spec/config.json Normal file
View File

@@ -0,0 +1,4 @@
{
"hostname": "github.com",
"repo": "higress-group/higress"
}

View File

@@ -36,7 +36,9 @@ header:
- 'pkg/ingress/kube/gateway/istio/testdata'
- 'release-notes/**'
- '.cursor/**'
- '.claude/**'
- '.claude/**'
- '.agents/**'
- '.issue-spec/**'
comment: on-failure
dependency:

156
AGENTS.md Normal file
View File

@@ -0,0 +1,156 @@
# AGENTS.md
Guidance for AI agents working in this repository.
Higress is a cloud-native API gateway built on Istio and Envoy. The control
plane extends Istio/pilot (Go); the data plane is Envoy extended with WASM
plugins (Go/Rust/C++/AssemblyScript) and a Go-based `golang-filter`. It supports
Ingress/Gateway API and ships a rich plugin ecosystem (including AI gateway
plugins).
## Repository layout
Top-level directories (all paths relative to repo root):
- `cmd/higress/` — main entrypoint (`main.go`) for the Higress controller binary.
- `pkg/` — core Go control-plane packages: `bootstrap/`, `cert/`, `cmd/`,
`common/`, `config/`, `ingress/` (Ingress/Gateway config translation),
`kube/`.
- `api/` — protobuf/CRD API definitions; Higress CRDs live in
`api/extensions/v1alpha1` (e.g. the `WasmPlugin` type). Generated with
`make gen-api` / `make gen-client` (see `api/gen.sh`, `buf.*`).
- `client/` — generated Go clientset for Higress CRDs.
- `istio/` — git submodules of higress-group forks of Istio (`api`, `istio`,
`client-go`, `pkg`, `proxy`); see `.gitmodules`. Pulled via `make submodule`
(part of `prebuild`).
- `envoy/` — Envoy + `go-control-plane` submodules (higress-group forks).
- `external/` — vendored/external mirror dirs used during build (istio, envoy,
proxy, etc.).
- `plugins/` — all data-plane plugins (see "Plugins" below).
- `registry/` — service-discovery registry integrations (nacos, consul, eureka,
zookeeper, direct, mcp, ...).
- `hgctl/` — the `hgctl` CLI (separate Go module) for managing Higress.
- `helm/` — Helm charts: `helm/core` (the dev/install chart) and `helm/higress`.
- `test/``test/e2e/` (conformance/e2e, see "Build & test") and
`test/gateway/`.
- `tools/` — build/CI scripting: `tools/hack/` (build scripts), `tools/bin/`,
`tools/linter/`, `*.mk`.
- `samples/` — example manifests (gateway-api, hello-world, wasmplugin, ...).
- `docker/`, `docs/`, `release-notes/` — packaging, docs, and release notes.
- `Makefile` — istio common-files wrapper (supports `BUILD_WITH_CONTAINER`);
real targets live in `Makefile.core.mk` (+ `Makefile.overrides.mk`).
## Plugins
All plugins live under `plugins/`. See `plugins/README.md` for the contributor
overview. Prebuilt plugin images are published to
`higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins`.
### plugins/wasm-go/ (primary WASM plugin framework, Go)
- `extensions/<name>/` — one directory per plugin (~59 plugins, many `ai-*`).
Each plugin is its own Go module: `main.go`, `go.mod`/`go.sum`, `VERSION`,
`README.md`(+`README_EN.md`), often `config/`, `util/`, `main_test.go`.
Optional `.buildrc` sets `EXTRA_TAGS`; optional `prepare.sh`/`prepare.sh`.
`plugin.wasm` is a build artifact and is **not** committed.
- Shared SDK: plugins depend on external modules
`github.com/higress-group/wasm-go` and
`github.com/higress-group/proxy-wasm-go-sdk` (NOT an in-repo SDK dir).
In-repo, `plugins/wasm-go/pkg/mcp/` provides MCP helpers and
`plugins/wasm-go/mcp-servers/` holds MCP server plugins.
- `examples/` — minimal reference plugins (custom-log, custom-span-attribute,
test-foreign-function).
- Build: `plugins/wasm-go/Makefile`. `PLUGIN_NAME=<name> make build` builds a
wasm file (output to `extensions/<name>/plugin.wasm`) + image via
`Dockerfile`/`DockerfileBuilder` (uses a `wasm-go-builder` image, Go 1.24,
TinyGo optional). `make build-push` pushes the image; `make local-build`
builds locally with `GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared`.
- Conventions: `VERSION` is the image tag; the CI/e2e batch builder
(`tools/hack/build-wasm-plugins.sh`) only compiles a wasm-go plugin whose
`VERSION` ends in `-alpha` (see the section at the bottom of this file).
### plugins/wasm-rust/ (Rust WASM plugins)
- Workspace-style: root `Cargo.toml`/`Cargo.lock`, shared `src/`,
`extensions/<name>/` per plugin (e.g. `ai-data-masking`, `ai-intent`,
`request-block`, `say-hello`, `demo-wasm`), `example/`.
- Build via `plugins/wasm-rust/Makefile` (`PLUGIN_NAME=<name> make build`, plus
`lint`/`test`); the batch builder runs it when `PLUGIN_TYPE=RUST`.
### plugins/wasm-cpp/ (C++ WASM plugins, Bazel)
- Bazel project: `WORKSPACE`, `BUILD`, `bazel/`, `common/`, `scripts/`,
`extensions/<name>/` (e.g. `basic_auth`, `jwt_auth`, `key_rate_limit`,
`model_router`, ...). Build via `plugins/wasm-cpp/Makefile`
(`PLUGIN_NAME=<name> make build`), invoked with `PLUGIN_TYPE=CPP`.
### plugins/wasm-assemblyscript/ (AssemblyScript WASM plugins)
- Node/AssemblyScript project: `asconfig.json`, `package.json`, `assembly/`,
`extensions/`.
### plugins/golang-filter/ (Envoy Go HTTP filter, NOT WASM)
- A native Envoy Golang HTTP filter (`main.go`, `mcp-server/`, `mcp-session/`);
compiled as a shared object (`.so`) independent of Envoy — no Envoy rebuild
needed. Requires Higress >= 2.1.0. Plugins register in `main.go`'s `init()`
via `RegisterHttpFilterFactoryAndConfigParser`. See
`plugins/golang-filter/README.md`.
- Build: `plugins/golang-filter/Makefile` (docker build, outputs
`golang-filter_<arch>.so`). Wired into the gateway image build via
`Makefile.core.mk` targets `build-golang-filter[-amd64|-arm64]`.
### How plugins are loaded
`WasmPlugin` CRDs (`extensions.higress.io/v1alpha1`) reference a plugin by
`url:` — either `oci://.../plugins/<name>:<version>` (image) or
`file:///opt/plugins/.../plugin.wasm` (local mount used in e2e). The dev install
`make install-dev-wasmplugin` sets Helm `global.volumeWasmPlugins=true` to mount
locally built wasm files into the gateway.
## Build & test
Run targets from the repo root; `Makefile` delegates to `Makefile.core.mk`.
Common ones:
- `make build` / `make build-linux` — build the Higress controller binary
(`prebuild` first fetches submodules).
- `make build-hgctl` — build the `hgctl` CLI.
- `make build-gateway` / `make build-istio` / `make build-envoy` — data-plane
and control-plane images (gateway pulls in the golang-filter).
- `make build-wasmplugins` — runs `tools/hack/build-wasm-plugins.sh` to batch
build WASM plugins (respects `PLUGIN_TYPE` / `PLUGIN_NAME`; Go plugins require
a `-alpha` VERSION).
- `make gen-api` / `make gen-client` — regenerate API/client code.
### Conformance / e2e tests (`test/e2e/`)
- Entrypoint `test/e2e/e2e_test.go`, run with build tag `conformance` and
`--test-area` / `--execute-tests` flags.
- Cases live in `test/e2e/conformance/tests/` as **paired `<name>.go` +
`<name>.yaml`** files (~68 cases; WASM cases are prefixed by language, e.g.
`go-wasm-*`, `cpp-wasm-*`). Support code: `conformance/base/`,
`conformance/utils/`, `conformance/embed.go`.
- Key Make targets (each spins up a kind cluster):
- `make higress-conformance-test` — Ingress/Gateway conformance.
- `make higress-wasmplugin-test` — WASM plugin e2e (uses
`install-dev-wasmplugin`, which builds plugins and mounts them).
- `*-prepare` / `*-skip-docker-build` / `*-clean` variants exist for
iterating; `run-higress-e2e-test[-wasmplugin]` runs `go test` against an
already-prepared cluster (filter with `TEST_SHORTNAME`).
- For the specifics of authoring a wasm-go e2e test, see the section below.
## Writing e2e conformance tests with wasm-go plugins
When adding an e2e conformance test that ships its own wasm-go plugin under
`plugins/wasm-go/extensions/<name>/`:
- The plugin's `VERSION` file **must end in `-alpha`** (e.g. `1.0.0-alpha`).
CI's `tools/hack/build-wasm-plugins.sh` only compiles a wasm-go plugin when
its version ends in `-alpha`; otherwise it silently skips it.
- `plugin.wasm` is a build artifact and is **not** committed. If the plugin
isn't built, the `file:///opt/plugins/.../plugin.wasm` URL in the test's
`WasmPlugin` manifest resolves to a missing file, envoy rejects the wasm
config and fails closed, and every request on that route returns HTTP 500.
Locally this can be masked because a previously built `plugin.wasm` still
exists on disk — so a test can pass locally yet 500 in CI.

View File

@@ -1,4 +1,24 @@
# Contributor Covenant Code of Conduct
# Higress Code of Conduct
Higress is a [Cloud Native Computing Foundation](https://www.cncf.io/) sandbox
project. As a CNCF project, the Higress community follows the
[**CNCF Code of Conduct**](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
The text below is the project's adopted Code of Conduct, based on the
[Contributor Covenant](https://www.contributor-covenant.org/), and is
substantively aligned with the CNCF Code of Conduct. Where any conflict exists,
the CNCF Code of Conduct prevails.
Instances of unacceptable behavior may be reported to the CNCF Code of
Conduct Committee at [conduct@cncf.io](mailto:conduct@cncf.io). For more
detailed instructions on how to submit a report, including how to submit a
report anonymously, please see the CNCF
[Incident Resolution Procedures](https://github.com/cncf/foundation/blob/main/code-of-conduct/coc-incident-resolution-procedures.md).
You can expect a response within three business days.
---
## Contributor Covenant Code of Conduct
## Our Pledge
@@ -55,7 +75,8 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at higress@googlegroups.com. All
reported by contacting the CNCF Code of Conduct Committee at
[conduct@cncf.io](mailto:conduct@cncf.io). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.

View File

@@ -23,7 +23,7 @@
## 报告安全问题
安全问题总是得到认真对待。作为我们通常的原则我们不鼓励任何人传播安全问题。如果您发现Higress的安全问题请不要公开讨论甚至不要公开问题。相反,我们鼓励您向 [higress@googlegroups.com](mailto:higress@googlegroups.com) 发送私人电子邮件 以报告此情况
安全问题总是得到认真对待。作为我们通常的原则,我们不鼓励任何人传播安全问题。如果您发现 Higress 的安全问题,请不要公开讨论,甚至不要公开 issue。请按照 [`SECURITY.md`](./SECURITY.md) 中描述的流程私密报告漏洞
## 报告一般问题
@@ -204,10 +204,18 @@ make prebuild && go mod tidy
任何测试用例都会受到欢迎。目前Higress 功能测试用例是高优先级的。
* 对于单元测试,您需要在同一模块的 test 目录中创建一个名为 xxxTest.go 的测试文件。
### 新功能的测试要求
* 对于集成测试,您可以将集成测试放在 test 目录
//TBD
- **新 Wasm 插件**:必须包含单元测试,代码覆盖率不低于 30%CI 强制检查)
- **新核心功能**:应包含单元测试,适用时还应添加 E2E 一致性测试用例。
- **Bug 修复**:应包含能复现该 Bug 的回归测试。
- **Patch 覆盖率**:新增或修改的代码必须达到 50% 的覆盖率(由 Codecov 通过 `codecov.yml` 强制检查)。
### 如何编写测试
* 对于单元测试,在同一模块的 test 目录中创建一个名为 `xxxTest.go` 的测试文件。
* 对于集成测试,将集成测试放在 test 目录。
* 对于 Wasm 插件 E2E 测试,在 `test/e2e/conformance/tests/` 中添加测试用例。详见 [test/README.md](./test/README.md)。
## 参与帮助任何事情

View File

@@ -23,7 +23,7 @@ Your interest in contributing to Higress is warmly welcomed. First, we encourage
## Reporting security issues
Security issues are always treated seriously. As our usual principle, we discourage anyone to spread security issues. If you find a security issue of Higress, please do not discuss it in public and even do not open a public issue. Instead we encourage you to send us a private email to [higress@googlegroups.com](mailto:higress@googlegroups.com) to report this.
Security issues are always treated seriously. As our usual principle, we discourage anyone to spread security issues. If you find a security issue of Higress, please do not discuss it in public and even do not open a public issue. Instead please follow the process described in [`SECURITY.md`](./SECURITY.md) to report vulnerabilities privately.
## Reporting general issues
@@ -204,9 +204,18 @@ make prebuild && go mod tidy
Any test case would be welcomed. Currently, Higress function test cases are high priority.
* For unit test, you need to create a test file named `xxxTest.go` in the test directory of the same module.
* For integration test, you can put the integration test in the test directory.
//TBD
### Test requirements for new functionality
- **New Wasm plugins**: MUST include unit tests with at least 30% code coverage (enforced by CI).
- **New core features**: SHOULD include unit tests and, where applicable, E2E conformance test cases.
- **Bug fixes**: SHOULD include a regression test that reproduces the bug.
- **Patch coverage**: New or changed code must meet a 50% coverage target for the patch (enforced by Codecov via `codecov.yml`).
### How to write tests
* For unit tests, create a test file named `xxxTest.go` in the test directory of the same module.
* For integration tests, you can put the integration test in the test directory.
* For Wasm plugin E2E tests, add test cases in `test/e2e/conformance/tests/`. See [test/README.md](./test/README.md) for details.
## Engage to help anything
We choose GitHub as the primary place for Higress to collaborate. So the latest updates of Higress are always here. Although contributions via PR is an explicit way to help, we still call for any other ways.

View File

@@ -23,7 +23,7 @@ Higress のハッキングに興味がある場合は、温かく歓迎します
## セキュリティ問題の報告
セキュリティ問題は常に真剣に扱われます。通常の原則として、セキュリティ問題を広めることは推奨しません。Higress のセキュリティ問題を発見した場合は、公開で議論せず、公開の問題を開かないでください。代わりに、[higress@googlegroups.com](mailto:higress@googlegroups.com) にプライベートなメールを送信して報告することをお勧めします
セキュリティ問題は常に真剣に扱われます。通常の原則として、セキュリティ問題を広めることは推奨しません。Higress のセキュリティ問題を発見した場合は、公開で議論せず、公開の issue を開かないでください。[`SECURITY.md`](./SECURITY.md) に記載されたプロセスに従って、脆弱性を非公開で報告してください
## 一般的な問題の報告
@@ -199,9 +199,18 @@ make prebuild && go mod tidy
テストケースは歓迎されます。現在、Higress の機能テストケースが高優先度です。
* 単体テストの場合、同じモジュールの test ディレクトリに xxxTest.go という名前のテストファイルを作成する必要があります。
* 統合テストの場合、統合テストを test ディレクトリに配置できます。
//TBD
### 新機能のテスト要件
- **新しい Wasm プラグイン**30% 以上のコードカバレッジを持つユニットテストを含める必要がありますCI で強制チェック)。
- **新しいコア機能**:ユニットテストを含めるべきであり、該当する場合は E2E コンフォーマンステストケースも追加すべきです。
- **バグ修正**:バグを再現する回帰テストを含めるべきです。
- **パッチカバレッジ**:新規または変更されたコードは 50% のカバレッジ目標を満たす必要がありますCodecov が `codecov.yml` を通じて強制チェック)。
### テストの書き方
* ユニットテストの場合、同じモジュールの test ディレクトリに `xxxTest.go` という名前のテストファイルを作成します。
* 統合テストの場合、統合テストを test ディレクトリに配置します。
* Wasm プラグイン E2E テストの場合、`test/e2e/conformance/tests/` にテストケースを追加します。詳細は [test/README.md](./test/README.md) を参照してください。
## 何かを手伝うための参加

50
GOVERNANCE.md Normal file
View File

@@ -0,0 +1,50 @@
# Higress Governance
Higress is a [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io/)
sandbox project. This document describes the project's open governance model.
All community members must follow the
[**CNCF Code of Conduct**](https://github.com/cncf/foundation/blob/main/code-of-conduct.md)
and the project's adopted policy in [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md).
## Values
Higress governance is guided by the following values:
- **Openness**: Communication and decision making happen in public channels and repositories whenever possible.
- **Fairness**: Contributions are evaluated on technical merit rather than company affiliation.
- **Community First**: Long-term community health has priority over short-term product goals.
- **Inclusivity**: We welcome contributors from different regions and backgrounds.
- **Participation**: Project responsibilities are earned through sustained contribution.
## Roles
Higress role definitions are documented in:
- [`MAINTAINERS.md`](./MAINTAINERS.md)
- [`CODEOWNERS`](./CODEOWNERS)
- [`CONTRIBUTING_EN.md`](./CONTRIBUTING_EN.md)
## Decision Making
Higress uses **lazy consensus** by default.
When consensus cannot be reached, maintainers may start a vote on a public
issue or pull request. A simple majority of votes cast decides the outcome.
For governance or project direction changes, maintainers should allow adequate
time for public discussion before finalizing decisions.
## Governance Updates
Changes to this document are made through pull requests and approved by
maintainers.
## Security
Security reporting and response follow [`SECURITY.md`](./SECURITY.md).
---
Higress is a [Cloud Native Computing Foundation](https://www.cncf.io/) sandbox
project.

38
MAINTAINERS.md Normal file
View File

@@ -0,0 +1,38 @@
# Higress Maintainers
This file lists the current maintainers of the Higress project.
A maintainer is a contributor who is actively responsible for guiding the
direction of the project, reviewing and merging contributions, and
participating in project governance.
For the day-to-day code-review ownership of individual subdirectories,
see [`CODEOWNERS`](./CODEOWNERS).
## Maintainers
| Name | GitHub | Affiliation |
| -------------- | ------------------------------------------------------- | ----------------- |
| Yiquan Dong | [@CH3CHO](https://github.com/CH3CHO) | Trip.com |
| Yuanxiao Zhao | [@EndlessSeeker](https://github.com/EndlessSeeker) | Alibaba Cloud |
| Leilei Geng | [@gengleilei](https://github.com/gengleilei) | Alibaba Cloud |
| Xiantao Han | [@hanxiantao](https://github.com/hanxiantao) | XinYe Technology |
| Zhiwei Cheng | [@cr7258](https://github.com/cr7258) | NVIDIA |
| Tianyi Zhang | [@johnlanni](https://github.com/johnlanni) | Alibaba Cloud |
| Jingfeng Xu | [@lexburner](https://github.com/lexburner) | Alibaba Cloud |
## Becoming a maintainer
Higress follows the contribution and graduation paths described in
[`CONTRIBUTING_EN.md`](./CONTRIBUTING_EN.md). Active and sustained
contributors who consistently demonstrate good technical judgement and
community stewardship can be nominated as maintainers by an existing
maintainer; nominations are accepted by lazy consensus among the current
maintainers.
## Reporting issues
* For security issues, please follow [`SECURITY.md`](./SECURITY.md).
* For all other questions, please use
[GitHub Issues](https://github.com/higress-group/higress/issues) or our
community channels listed in the [README](./README.md).

View File

@@ -23,6 +23,11 @@ export GOPROXY ?= https://proxy.golang.org,direct
TARGET_ARCH ?= amd64
VALID_ARCHS := amd64 arm64
ifeq ($(filter $(TARGET_ARCH),$(VALID_ARCHS)),)
$(error "TARGET_ARCH must be one of: $(VALID_ARCHS)")
endif
GOARCH_LOCAL := $(TARGET_ARCH)
GOOS_LOCAL := $(TARGET_OS)
RELEASE_LDFLAGS='$(GO_LDFLAGS) -extldflags -static -s -w'
@@ -146,7 +151,7 @@ docker-buildx-push: clean-env docker.higress-buildx
export PARENT_GIT_TAG:=$(shell cat VERSION)
export PARENT_GIT_REVISION:=$(TAG)
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.2.1/envoy-symbol-ARCH.tar.gz
export ENVOY_PACKAGE_URL_PATTERN?=https://github.com/higress-group/proxy/releases/download/v2.2.3/envoy-symbol-ARCH.tar.gz
build-envoy: prebuild
./tools/hack/build-envoy.sh
@@ -166,7 +171,7 @@ build-gateway: prebuild buildx-prepare build-golang-filter
USE_REAL_USER=1 TARGET_ARCH=arm64 DOCKER_TARGETS="docker.proxyv2" ./tools/hack/build-istio-image.sh init
DOCKER_TARGETS="docker.proxyv2" IMG_URL="${IMG_URL}" ./tools/hack/build-istio-image.sh docker.buildx
build-gateway-local: prebuild build-golang-filter-amd64
build-gateway-local: prebuild $(if $(filter amd64,$(TARGET_ARCH)),build-golang-filter-amd64,build-golang-filter-arm64)
TARGET_ARCH=${TARGET_ARCH} DOCKER_TARGETS="docker.proxyv2" ./tools/hack/build-istio-image.sh docker
build-golang-filter-amd64:
@@ -200,8 +205,8 @@ install: pre-install
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
HIGRESS_LATEST_IMAGE_TAG ?= latest
ENVOY_LATEST_IMAGE_TAG ?= ca6ff3a92e3fa592bff706894b22e0509a69757b
ISTIO_LATEST_IMAGE_TAG ?= c482b42b9a14885bd6692c6abd01345d50a372f7
ENVOY_LATEST_IMAGE_TAG ?= 91244c578aef498af93cacb2cf353f3878b92fc4
ISTIO_LATEST_IMAGE_TAG ?= de2c9628294f51b13c4a70b3a862b4372890797a
install-dev: pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'

View File

@@ -7,12 +7,14 @@
<h4 align="center"> AI Native API Gateway </h4>
<div align="center">
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![discord](https://img.shields.io/discord/1364956090566971515?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square)](https://discord.gg/tSbww9VDaM)
[![CNCF Sandbox](https://img.shields.io/badge/CNCF-Sandbox-30638E?logo=linuxfoundation&logoColor=white)](https://www.cncf.io/projects/)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12667/badge)](https://www.bestpractices.dev/projects/12667)
<a href="https://trendshift.io/repositories/10918" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10918" alt="alibaba%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://www.producthunt.com/posts/higress?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-higress" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=951287&theme=light&t=1745492822283" alt="Higress - Global&#0032;APIs&#0032;as&#0032;MCP&#0032;powered&#0032;by&#0032;AI&#0032;Gateway | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://trendshift.io/repositories/26458" target="_blank"><img src="https://trendshift.io/api/badge/repositories/26458" alt="higress-group%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://www.producthunt.com/posts/higress?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-higress" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=951287&theme=light&t=1745492822283" alt="Higress - Global&#0032;APIs&#0032;as&#0032;MCP&#0032;powered&#0032;by&#0032;AI&#0032;Gateway | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
@@ -33,7 +35,7 @@ Higress is a cloud-native API gateway based on Istio and Envoy, which can be ext
### Core Use Cases
Higress's AI gateway capabilities support all [mainstream model providers](https://github.com/alibaba/higress/tree/main/plugins/wasm-go/extensions/ai-proxy/provider) both domestic and international. It also supports hosting MCP (Model Context Protocol) Servers through its plugin mechanism, enabling AI Agents to easily call various tools and services. With the [openapi-to-mcp tool](https://github.com/higress-group/openapi-to-mcpserver), you can quickly convert OpenAPI specifications into remote MCP servers for hosting. Higress provides unified management for both LLM API and MCP API.
Higress's AI gateway capabilities support all [mainstream model providers](https://github.com/alibaba/higress/tree/main/plugins/wasm-go/extensions/ai-proxy/provider) both domestic and international. It also supports hosting MCP (Model Context Protocol) Servers through its plugin mechanism, enabling AI Agents to easily call various tools and services. With the [openapi-to-mcp tool](https://github.com/higress-group/openapi-to-mcpserver), you can quickly convert OpenAPI specifications into remote MCP servers for hosting. Higress provides unified management for both LLM API and MCP API.
**🌟 Try it now at [https://mcp.higress.ai/](https://mcp.higress.ai/)** to experience Higress-hosted Remote MCP Servers firsthand:
@@ -48,11 +50,9 @@ You can click the button below to install the enterprise version of Higress:
[![Deploy on AlibabaCloud](https://img.alicdn.com/imgextra/i1/O1CN01e6vwe71EWTHoZEcpK_!!6000000000359-55-tps-170-40.svg)](https://www.aliyun.com/product/api-gateway?spm=higress-github.topbar.0.0.0)
If you use open-source Higress and wish to obtain enterprise-level support, you can contact the project maintainer johnlanni's email: **zty98751@alibaba-inc.com** or social media accounts (WeChat ID: **nomadao**, DingTalk ID: **chengtanzty**). Please note **Higress** when adding as a friend :)
## Summary
- [**Quick Start**](#quick-start)
- [**Quick Start**](#quick-start)
- [**Feature Showcase**](#feature-showcase)
- [**Use Cases**](#use-cases)
- [**Core Advantages**](#core-advantages)
@@ -79,14 +79,26 @@ Port descriptions:
> All Higress Docker images use Higress's own image repository and are not affected by Docker Hub rate limits.
> In addition, the submission and updates of the images are protected by a security scanning mechanism (powered by Alibaba Cloud ACR), making them very secure for use in production environments.
>
>
> If you experience a timeout when pulling image from `higress-registry.cn-hangzhou.cr.aliyuncs.com`, you can try replacing it with the following docker registry mirror source:
>
>
> **North America**: `higress-registry.us-west-1.cr.aliyuncs.com`
>
>
> **Southeast Asia**: `higress-registry.ap-southeast-7.cr.aliyuncs.com`
For other installation methods such as Helm deployment under K8s, please refer to the official [Quick Start documentation](https://higress.io/en-us/docs/user/quickstart).
> **For Kubernetes deployments**, you can configure the `global.hub` parameter in Helm values to use a mirror registry closer to your region. This applies to both Higress component images and built-in Wasm plugin images:
>
> ```bash
> # Example: Using North America mirror
> helm install higress -n higress-system higress.io/higress --set global.hub=higress-registry.us-west-1.cr.aliyuncs.com --create-namespace
> ```
>
> Available mirror registries:
> - **China (Hangzhou)**: `higress-registry.cn-hangzhou.cr.aliyuncs.com` (default)
> - **North America**: `higress-registry.us-west-1.cr.aliyuncs.com`
> - **Southeast Asia**: `higress-registry.ap-southeast-7.cr.aliyuncs.com`
For other installation methods such as Helm deployment under K8s, please refer to the official [Quick Start documentation](https://higress.ai/en/docs/latest/user/quickstart/).
If you are deploying on the cloud, it is recommended to use the [Enterprise Edition](https://www.aliyun.com/product/apigateway?spm=higress-github.topbar.0.0.0)
@@ -118,7 +130,7 @@ If you are deploying on the cloud, it is recommended to use the [Enterprise Edit
- **Kubernetes ingress controller**:
Higress can function as a feature-rich ingress controller, which is compatible with many annotations of K8s' nginx ingress controller.
[Gateway API](https://gateway-api.sigs.k8s.io/) is already supported, and it supports a smooth migration from Ingress API to Gateway API.
Compared to ingress-nginx, the resource overhead has significantly decreased, and the speed at which route changes take effect has improved by ten times.
@@ -129,13 +141,13 @@ If you are deploying on the cloud, it is recommended to use the [Enterprise Edit
![](https://img.alicdn.com/imgextra/i1/O1CN01bhEtb229eeMNBWmdP_!!6000000008093-2-tps-750-547.png)
- **Microservice gateway**:
Higress can function as a microservice gateway, which can discovery microservices from various service registries, such as Nacos, ZooKeeper, Consul, Eureka, etc.
It deeply integrates with [Dubbo](https://github.com/apache/dubbo), [Nacos](https://github.com/alibaba/nacos), [Sentinel](https://github.com/alibaba/Sentinel) and other microservice technology stacks.
- **Security gateway**:
Higress can be used as a security gateway, supporting WAF and various authentication strategies, such as key-auth, hmac-auth, jwt-auth, basic-auth, oidc, etc.
@@ -154,15 +166,15 @@ If you are deploying on the cloud, it is recommended to use the [Enterprise Edit
Supports true complete streaming processing of request/response bodies, Wasm plugins can easily customize the handling of streaming protocols such as SSE (Server-Sent Events).
In high-bandwidth scenarios such as AI businesses, it can significantly reduce memory overhead.
- **Easy to Extend**
Provides a rich official plugin library covering AI, traffic management, security protection and other common functions, meeting more than 90% of business scenario requirements.
Focuses on Wasm plugin extensions, ensuring memory safety through sandbox isolation, supporting multiple programming languages, allowing plugin versions to be upgraded independently, and achieving traffic-lossless hot updates of gateway logic.
- **Secure and Easy to Use**
Based on Ingress API and Gateway API standards, provides out-of-the-box UI console, WAF protection plugin, IP/Cookie CC protection plugin ready to use.
Supports connecting to Let's Encrypt for automatic issuance and renewal of free certificates, and can be deployed outside of K8s, started with a single Docker command, convenient for individual developers to use.
@@ -173,6 +185,25 @@ Join our Discord community! This is where you can connect with developers and ot
[![discord](https://img.shields.io/discord/1364956090566971515?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge)](https://discord.gg/tSbww9VDaM)
### Code of Conduct
The Higress community follows the
[**CNCF Code of Conduct**](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
The project's adopted Code of Conduct is documented in
[`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md). Please review it before
participating in the community.
### Governance & Maintainers
Project governance, the maintainer roster, and the contribution model are
described in [`GOVERNANCE.md`](./GOVERNANCE.md) and
[`MAINTAINERS.md`](./MAINTAINERS.md). New contributors are encouraged to start
with [`CONTRIBUTING_EN.md`](./CONTRIBUTING_EN.md).
### Security
Please report security vulnerabilities following the process described in
[`SECURITY.md`](./SECURITY.md).
### Thanks
@@ -195,6 +226,23 @@ Higress would not be possible without the valuable open-source work of projects
[![Star History Chart](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date)
---
## Cloud Native Computing Foundation
<a href="https://www.cncf.io/projects/" target="_blank">
<img src="https://raw.githubusercontent.com/cncf/artwork/master/other/cncf/horizontal/color/cncf-color.svg" alt="Cloud Native Computing Foundation" width="300"/>
</a>
We are a [Cloud Native Computing Foundation](https://www.cncf.io/) sandbox project.
The Linux Foundation® (TLF) has registered trademarks and uses trademarks. For
a list of TLF trademarks, see [Trademark Usage](https://www.linuxfoundation.org/legal/trademark-usage).
Copyright Higress a Series of LF Projects, LLC. For website terms of use,
trademark policy and other project policies please see
[https://lfprojects.org/policies/](https://lfprojects.org/policies/).
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
↑ Back to Top ↑

View File

@@ -8,6 +8,8 @@
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![CNCF Sandbox](https://img.shields.io/badge/CNCF-Sandbox-30638E?logo=linuxfoundation&logoColor=white)](https://www.cncf.io/projects/)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12667/badge)](https://www.bestpractices.dev/projects/12667)
[**公式サイト**](https://higress.cn/) &nbsp; |
&nbsp; [**ドキュメント**](https://higress.cn/docs/latest/overview/what-is-higress/) &nbsp; |
@@ -190,6 +192,23 @@ K8sでのHelmデプロイなどの他のインストール方法については
## コミュニティ
### 行動規範Code of Conduct
Higress コミュニティは
[**CNCF Code of Conduct**](https://github.com/cncf/foundation/blob/main/code-of-conduct.md)
に従っています。プロジェクトが採用している行動規範は
[`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) に記載されていますので、コミュニティに参加する前にご確認ください。
### ガバナンスとメンテナー
プロジェクトのガバナンス、メンテナー一覧、コントリビューションモデルについては
[`GOVERNANCE.md`](./GOVERNANCE.md) と [`MAINTAINERS.md`](./MAINTAINERS.md) を参照してください。
初めて貢献される方は [`CONTRIBUTING_EN.md`](./CONTRIBUTING_EN.md) からお読みください。
### セキュリティ
セキュリティに関する脆弱性は [`SECURITY.md`](./SECURITY.md) に記載された手順に従って報告してください。
### 感謝
EnvoyとIstioのオープンソースの取り組みがなければ、Higressは実現できませんでした。これらのプロジェクトに最も誠実な敬意を表します。
@@ -221,6 +240,23 @@ WeChat公式アカウント
[![スターの歴史チャート](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date)
---
## Cloud Native Computing Foundation
<a href="https://www.cncf.io/projects/" target="_blank">
<img src="https://raw.githubusercontent.com/cncf/artwork/master/other/cncf/horizontal/color/cncf-color.svg" alt="Cloud Native Computing Foundation" width="300"/>
</a>
Higress は [Cloud Native Computing Foundation](https://www.cncf.io/) のサンドボックスプロジェクトです。
The Linux Foundation® (TLF) は登録商標を所有し使用しています。LF の商標一覧については
[Trademark Usage](https://www.linuxfoundation.org/legal/trademark-usage) をご参照ください。
Copyright Higress a Series of LF Projects, LLC. ウェブサイトの利用規約、商標ポリシー、
その他のプロジェクトポリシーについては
[https://lfprojects.org/policies/](https://lfprojects.org/policies/) をご参照ください。
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
↑ トップに戻る ↑

View File

@@ -10,8 +10,10 @@
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![CNCF Sandbox](https://img.shields.io/badge/CNCF-Sandbox-30638E?logo=linuxfoundation&logoColor=white)](https://www.cncf.io/projects/)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12667/badge)](https://www.bestpractices.dev/projects/12667)
<a href="https://trendshift.io/repositories/10918" target="_blank"><img src="https://trendshift.io/api/badge/repositories/10918" alt="alibaba%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://www.producthunt.com/posts/higress?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-higress" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=951287&theme=light&t=1745492822283" alt="Higress - Global&#0032;APIs&#0032;as&#0032;MCP&#0032;powered&#0032;by&#0032;AI&#0032;Gateway | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://trendshift.io/repositories/26458" target="_blank"><img src="https://trendshift.io/api/badge/repositories/26458" alt="higress-group%2Fhigress | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a> <a href="https://www.producthunt.com/posts/higress?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-higress" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=951287&theme=light&t=1745492822283" alt="Higress - Global&#0032;APIs&#0032;as&#0032;MCP&#0032;powered&#0032;by&#0032;AI&#0032;Gateway | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
[**官网**](https://higress.cn/) &nbsp; |
@@ -80,6 +82,24 @@ docker run -d --rm --name higress-ai -v ${PWD}:/data \
**Higress 的所有 Docker 镜像都一直使用自己独享的仓库,不受 Docker Hub 境内访问受限的影响**
> 如果从 `higress-registry.cn-hangzhou.cr.aliyuncs.com` 拉取镜像超时,可以尝试使用以下镜像加速源:
>
> **北美**: `higress-registry.us-west-1.cr.aliyuncs.com`
>
> **东南亚**: `higress-registry.ap-southeast-7.cr.aliyuncs.com`
> **K8s 部署时**,可以通过 Helm values 配置 `global.hub` 参数来使用距离部署区域更近的镜像仓库,该参数会同时应用于 Higress 组件镜像和内置 Wasm 插件镜像:
>
> ```bash
> # 示例:使用北美镜像源
> helm install higress -n higress-system higress.io/higress --set global.hub=higress-registry.us-west-1.cr.aliyuncs.com --create-namespace
> ```
>
> 可用镜像仓库:
> - **中国(杭州)**: `higress-registry.cn-hangzhou.cr.aliyuncs.com`(默认)
> - **北美**: `higress-registry.us-west-1.cr.aliyuncs.com`
> - **东南亚**: `higress-registry.ap-southeast-7.cr.aliyuncs.com`
K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start 文档](https://higress.cn/docs/latest/user/quickstart/)。
如果您是在云上部署,推荐使用[企业版](https://www.aliyun.com/product/apigateway?spm=higress-github.topbar.0.0.0)
@@ -203,6 +223,21 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start
## 社区
### 行为准则Code of Conduct
Higress 社区遵循 [**CNCF Code of Conduct**](https://github.com/cncf/foundation/blob/main/code-of-conduct.md)。
项目采纳的行为准则详见 [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md),参与社区前请先阅读。
### 治理与维护者
项目治理、维护者名单与贡献模型分别记录于 [`GOVERNANCE.md`](./GOVERNANCE.md) 与
[`MAINTAINERS.md`](./MAINTAINERS.md),新贡献者请先查阅
[`CONTRIBUTING_CN.md`](./CONTRIBUTING_CN.md)。
### 安全
如发现安全漏洞,请按照 [`SECURITY.md`](./SECURITY.md) 中的流程进行报告。
### 感谢
如果没有 Envoy 和 Istio 的开源工作Higress 就不可能实现,在这里向这两个项目献上最诚挚的敬意。
@@ -234,6 +269,22 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start
[![Star History](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date)
---
## 云原生计算基金会CNCF
<a href="https://www.cncf.io/projects/" target="_blank">
<img src="https://raw.githubusercontent.com/cncf/artwork/master/other/cncf/horizontal/color/cncf-color.svg" alt="Cloud Native Computing Foundation" width="300"/>
</a>
Higress 是 [Cloud Native Computing Foundation](https://www.cncf.io/) 的沙箱Sandbox项目。
The Linux Foundation® (TLF) 拥有相关注册商标并对其加以使用。完整的 LF 商标清单见
[Trademark Usage](https://www.linuxfoundation.org/legal/trademark-usage)。
Copyright Higress a Series of LF Projects, LLC. 项目相关的网站使用条款、商标策略及其他政策,
请参见 [https://lfprojects.org/policies/](https://lfprojects.org/policies/)。
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
↑ 返回顶部 ↑

View File

@@ -1,15 +1,86 @@
# Security Policy
The Higress team takes security seriously. We appreciate your efforts to
responsibly disclose your findings and will make every effort to acknowledge
your contributions.
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.x.x | :white_check_mark: |
| 1.x.x | :white_check_mark: |
| < 1.0.0 | :x: |
| < 1.0.0 | :x: |
## Reporting a Vulnerability
Please report any security issue or Higress crash report to [ASRC](https://security.alibaba.com/)(Alibaba Security Response Center) where the issue will be triaged appropriately.
**Please do NOT report security vulnerabilities through public GitHub issues,
discussions, or pull requests.**
Thank you in advance for helping to keep Higress secure.
Instead, please report them through **both** of the following private channels:
1. **GitHub Private Security Advisory**:
<https://github.com/higress-group/higress/security/advisories/new>
2. **Alibaba Security Response Center (ASRC)**:
<https://security.alibaba.com/>
Please include as much of the following information as possible to help us
triage and address the issue:
- Type of issue (e.g., buffer overflow, injection, privilege escalation, etc.)
- Full paths of source file(s) related to the issue (if known)
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code (if possible)
- Impact of the issue, including how an attacker might exploit it
- Any suggested fix or mitigation (if available)
## Response Process
The Higress security team will follow these steps upon receiving a report:
1. **Acknowledgement**: We will acknowledge receipt of your report within
**3 business days**.
2. **Triage**: We will evaluate the report, confirm the vulnerability, and
determine its severity and impact within **14 days**.
3. **Fix Development**: We will develop a fix and coordinate with you on an
appropriate disclosure timeline.
4. **Disclosure**: We will publish a security advisory via
[GitHub Security Advisories](https://github.com/higress-group/higress/security/advisories)
and credit you for the discovery (unless you prefer to remain anonymous).
We aim to resolve critical vulnerabilities as quickly as possible and will
keep you informed of our progress throughout the process.
## Security Response Team
The Higress security response is handled by the project maintainers listed in
[`MAINTAINERS.md`](./MAINTAINERS.md). Security reports submitted via GitHub
Private Security Advisory are visible to all current maintainers.
## Disclosure Policy
We follow a coordinated disclosure process:
- We ask reporters to give us a reasonable amount of time to address the issue
before any public disclosure.
- We will work with you to agree on a disclosure timeline, typically **90 days**
from the initial report.
- We will publish security advisories and, where appropriate, request CVE
identifiers for confirmed vulnerabilities.
- We will credit reporters in the advisory unless they request anonymity.
## Security-Related Configuration
For guidance on securely deploying and configuring Higress, please refer to
the [official documentation](https://higress.cn/en/docs/latest/overview/what-is-higress/).
Key security features include:
- Built-in WAF protection plugin
- Authentication plugins (key-auth, hmac-auth, jwt-auth, basic-auth, OIDC)
- IP/Cookie-based CC protection
- TLS termination with automatic Let's Encrypt certificate management
---
Higress is a [Cloud Native Computing Foundation](https://www.cncf.io/)
sandbox project.

View File

@@ -1 +1 @@
v2.2.0
v2.2.3

View File

@@ -18,7 +18,7 @@ import (
"fmt"
"os"
"istio.io/pkg/log"
"istio.io/istio/pkg/log"
"github.com/alibaba/higress/v2/pkg/cmd"
)

View File

@@ -4,7 +4,7 @@ ARG BASE_DISTRIBUTION=debug
# Version is the base image version from the TLD Makefile
ARG BASE_VERSION=latest
ARG HUB
ARG HUB=higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
ARG TARGETARCH

View File

@@ -14,7 +14,7 @@ Higress Console 是 Higress 网关的管理控制台,主要功能是管理 Hig
### 1.1 Higress Admin SDK
Higress Admin SDK 脱胎于 Higress Console。起初它作为 Higress Console 的一部分,为前端界面提供实际的功能支持。后来考虑到对接外部系统等需求,将配置管理的部分剥离出来,形成一个独立的逻辑组件,便于和各个系统进行对接。目前支持服务来源管理、服务管理、路由管理、域名管理、证书管理、插件管理等功能。
Higress Admin SDK 现在只提供 Java 版本,且要求 JDK 版本不低于 17。具体如何集成请参考 Higress 官方 BLOG [如何使用 Higress Admin SDK 进行配置管理](https://higress.io/zh-cn/blog/admin-sdk-intro)。
Higress Admin SDK 现在只提供 Java 版本,且要求 JDK 版本不低于 17。具体如何集成请参考 Higress 官方 BLOG [如何使用 Higress Admin SDK 进行配置管理](https://higress.ai/blog/admin-sdk-intro)。
## 2 Higress Controller

2
go.mod
View File

@@ -31,7 +31,7 @@ require (
github.com/hudl/fargo v1.4.0
github.com/mholt/acmez v1.2.0
github.com/nacos-group/nacos-sdk-go v1.0.8
github.com/nacos-group/nacos-sdk-go/v2 v2.3.2
github.com/nacos-group/nacos-sdk-go/v2 v2.3.5
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.7
github.com/stretchr/testify v1.11.1

4
go.sum
View File

@@ -3688,8 +3688,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nacos-group/nacos-sdk-go v1.0.8 h1:8pEm05Cdav9sQgJSv5kyvlgfz0SzFUUGI3pWX6SiSnM=
github.com/nacos-group/nacos-sdk-go v1.0.8/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA=
github.com/nacos-group/nacos-sdk-go/v2 v2.3.2 h1:9QB2nCJzT5wkTVlxNYl3XL/7+G6p2USMi2gQh/ouQQo=
github.com/nacos-group/nacos-sdk-go/v2 v2.3.2/go.mod h1:9FKXl6FqOiVmm72i8kADtbeK71egyG9y3uRDBg41tpQ=
github.com/nacos-group/nacos-sdk-go/v2 v2.3.5 h1:Hux7C4N4rWhwBF5Zm4yyYskrs9VTgrRTA8DZjoEhQTs=
github.com/nacos-group/nacos-sdk-go/v2 v2.3.5/go.mod h1:ygUBdt7eGeYBt6Lz2HO3wx7crKXk25Mp80568emGMWU=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.2.0
appVersion: 2.2.3
description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -15,4 +15,4 @@ dependencies:
repository: "file://../redis"
version: 0.0.1
type: application
version: 2.2.0
version: 2.2.3

View File

@@ -23,7 +23,7 @@ spec:
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.global.hub }}/{{ .Values.redis.image | default "redis-stack-server" }}:{{ .Values.redis.tag | default .Chart.AppVersion }}"
image: "{{ .Values.global.hub }}/higress/{{ .Values.redis.image | default "redis-stack-server" }}:{{ .Values.redis.tag | default .Chart.AppVersion }}"
{{- if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
{{- end }}

View File

@@ -39,7 +39,12 @@ template:
{{- end }}
containers:
- name: higress-gateway
image: "{{ .Values.gateway.hub | default .Values.global.hub }}/{{ .Values.gateway.image | default "gateway" }}:{{ .Values.gateway.tag | default .Chart.AppVersion }}"
image: "{{ .Values.gateway.hub | default .Values.global.hub }}/higress/{{ .Values.gateway.image | default "gateway" }}:{{ .Values.gateway.tag | default .Chart.AppVersion }}"
{{- if .Values.gateway.imagePullPolicy }}
imagePullPolicy: {{ .Values.gateway.imagePullPolicy }}
{{- else if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
{{- end }}
args:
- proxy
- router
@@ -205,8 +210,10 @@ template:
{{- if $o11y.enabled }}
{{- $config := $o11y.promtail }}
- name: promtail
image: {{ $config.image.repository | default (printf "%s/promtail" .Values.global.hub) }}:{{ $config.image.tag }}
imagePullPolicy: IfNotPresent
image: {{ $config.image.repository | default (printf "%s/higress/promtail" .Values.global.hub) }}:{{ $config.image.tag }}
{{- if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
{{- end }}
args:
- -config.file=/etc/promtail/promtail.yaml
env:

View File

@@ -38,7 +38,12 @@ spec:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.controller.securityContext | nindent 12 }}
image: "{{ .Values.controller.hub | default .Values.global.hub }}/{{ .Values.controller.image | default "higress" }}:{{ .Values.controller.tag | default .Chart.AppVersion }}"
image: "{{ .Values.controller.hub | default .Values.global.hub }}/higress/{{ .Values.controller.image | default "higress" }}:{{ .Values.controller.tag | default .Chart.AppVersion }}"
{{- if .Values.controller.imagePullPolicy }}
imagePullPolicy: {{ .Values.controller.imagePullPolicy }}
{{- else if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
{{- end }}
args:
- "serve"
- --gatewaySelectorKey=higress
@@ -49,6 +54,7 @@ spec:
- --enableStatus={{ .Values.global.enableStatus }}
{{- end }}
- --ingressClass={{ .Values.global.ingressClass }}
- --gatewayClass={{ .Values.global.gatewayClass }}
{{- if .Values.global.watchNamespace }}
- --watchNamespace={{ .Values.global.watchNamespace }}
{{- end }}
@@ -77,7 +83,7 @@ spec:
- name: PILOT_ENABLE_GATEWAY_API
value: "{{ .Values.global.enableGatewayAPI }}"
- name: PILOT_ENABLE_ALPHA_GATEWAY_API
value: "{{ .Values.global.enableGatewayAPI }}"
value: "{{ .Values.global.enableAlphaGatewayAPI }}"
{{- if .Values.global.enableInferenceExtension }}
- name: ENABLE_GATEWAY_API_INFERENCE_EXTENSION
value: "true"
@@ -104,8 +110,10 @@ spec:
- name: log
mountPath: /var/log
- name: discovery
image: "{{ .Values.pilot.hub | default .Values.global.hub }}/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Chart.AppVersion }}"
{{- if .Values.global.imagePullPolicy }}
image: "{{ .Values.pilot.hub | default .Values.global.hub }}/higress/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Chart.AppVersion }}"
{{- if .Values.controller.imagePullPolicy }}
imagePullPolicy: {{ .Values.controller.imagePullPolicy }}
{{- else if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
{{- end }}
args:

View File

@@ -1,8 +1,8 @@
{{- if .Values.global.ingressClass }}
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: {{ .Values.global.ingressClass }}
spec:
controller: higress.io/higress-controller
{{- end }}
{{- if and .Values.global.ingressClass .Values.global.createIngressClass }}
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: {{ .Values.global.ingressClass }}
spec:
controller: higress.io/higress-controller
{{- end }}

View File

@@ -23,8 +23,10 @@ spec:
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: {{ .Values.pluginServer.hub | default .Values.global.hub }}/{{ .Values.pluginServer.image | default "plugin-server" }}:{{ .Values.pluginServer.tag | default "1.0.0" }}
{{- if .Values.global.imagePullPolicy }}
image: {{ .Values.pluginServer.hub | default .Values.global.hub }}/higress/{{ .Values.pluginServer.image | default "plugin-server" }}:{{ .Values.pluginServer.tag | default .Chart.Version }}
{{- if .Values.pluginServer.imagePullPolicy }}
imagePullPolicy: {{ .Values.pluginServer.imagePullPolicy }}
{{- else if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
{{- end }}
ports:

View File

@@ -25,6 +25,13 @@ global:
# 2. When the ingress class is set empty, the higress controller will watch all ingress
# resources in the k8s cluster.
ingressClass: "higress"
# -- Whether to create the IngressClass resource for global.ingressClass.
# Set this to false when reusing an existing IngressClass, for example during Nginx Ingress migration.
createIngressClass: true
# -- GatewayClassName used by Higress to select Gateway API resources.
# The default value higress uses controllerName higress.io/gateway-controller.
# A custom value, for example higress-internal, uses controllerName higress.io/gateway-controller-higress-internal.
gatewayClass: "higress"
# -- If not empty, Higress Controller will only watch resources in the specified namespace.
# When isolating different business systems using K8s namespace,
# if each namespace requires a standalone gateway instance,
@@ -47,6 +54,8 @@ global:
enableIstioAPI: true
# -- If true, Higress Controller will monitor Gateway API resources as well
enableGatewayAPI: true
# -- If true, Higress Controller will monitor Gateway API resources that have not reached v1 yet
enableAlphaGatewayAPI: false
# -- If true, enable Gateway API Inference Extension support
enableInferenceExtension: false
# -- Used to locate istiod.
@@ -70,10 +79,14 @@ global:
# cpu: 100m
# memory: 128Mi
# -- Default hub for Istio images.
# Releases are published to docker hub under 'istio' project.
# Dev builds from prow are on gcr.io
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
# -- Default hub (registry) for Higress images.
# For Higress deployments, images are pulled from: {hub}/higress/{image}
# For built-in plugins, images are pulled from: {hub}/{pluginNamespace}/{plugin-name}
# Change this to use a mirror registry closer to your deployment region for faster image pulls.
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com
# -- Namespace for built-in plugin images. Default is "plugins".
# Used by higress-console to configure plugin image path.
pluginNamespace: "plugins"
# -- Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
@@ -419,6 +432,10 @@ gateway:
replicas: 2
image: gateway
# -- Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
imagePullPolicy: ""
# -- Use a `DaemonSet` or `Deployment`
kind: Deployment
@@ -573,6 +590,10 @@ controller:
periodSeconds: 3
timeoutSeconds: 5
# -- Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
imagePullPolicy: ""
imagePullSecrets: []
rbac:
@@ -648,13 +669,6 @@ controller:
## -- Discovery Settings
pilot:
autoscaleEnabled: false
autoscaleMin: 1
autoscaleMax: 5
replicaCount: 1
rollingMaxSurge: 100%
rollingMaxUnavailable: 25%
hub: "" # Will use global.hub if not set
tag: ""
@@ -682,21 +696,11 @@ pilot:
# -- if protocol sniffing is enabled for inbound
enableProtocolSniffingForInbound: true
nodeSelector: {}
podAnnotations: {}
serviceAnnotations: {}
# -- You can use jwksResolverExtraRootCA to provide a root certificate
# in PEM format. This will then be trusted by pilot when resolving
# JWKS URIs.
jwksResolverExtraRootCA: ""
# -- This is used to set the source of configuration for
# the associated address in configSource, if nothing is specified
# the default MCP is assumed.
configSource:
subscribedResources: []
plugins: []
# -- The following is used to limit how long a sidecar can be connected
@@ -704,18 +708,6 @@ pilot:
# increasing system churn.
keepaliveMaxServerConnectionAge: 30m
# -- Additional labels to apply to the deployment.
deploymentLabels: {}
## Mesh config settings
# -- Install the mesh config map, generated from values.yaml.
# If false, pilot wil use default values (by default) or user-supplied values.
configMap: true
# -- Additional labels to apply on the pod level for monitoring and logging configuration.
podLabels: {}
# Tracing config settings
tracing:
enable: false
@@ -811,6 +803,10 @@ pluginServer:
hub: "" # Will use global.hub if not set
tag: ""
# -- Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
imagePullPolicy: ""
imagePullSecrets: []
labels: {}

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 2.2.0
version: 2.2.3
- name: higress-console
repository: https://higress.io/helm-charts/
version: 2.2.0
digest: sha256:2cb148fa6d52856344e1905d3fea018466c2feb52013e08997c2d5c7d50f2e5d
generated: "2026-02-11T17:45:59.187965929+08:00"
version: 2.2.3
digest: sha256:b2089e3b943edcf5cfbb52c42fbf00fad58171460a89e5a20c88cd7657f75419
generated: "2026-06-24T19:48:33.129399+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 2.2.0
appVersion: 2.2.3
description: Helm chart for deploying Higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -12,9 +12,9 @@ sources:
dependencies:
- name: higress-core
repository: "file://../core"
version: 2.2.0
version: 2.2.3
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 2.2.0
version: 2.2.3
type: application
version: 2.2.0
version: 2.2.3

View File

@@ -46,6 +46,7 @@ The command removes all the Kubernetes components associated with the chart and
| controller.env | object | `{}` | |
| controller.hub | string | `""` | |
| controller.image | string | `"higress"` | |
| controller.imagePullPolicy | string | `""` | Specify image pull policy if default behavior isn't desired. Default behavior: latest images will be Always else IfNotPresent. |
| controller.imagePullSecrets | list | `[]` | |
| controller.labels | object | `{}` | |
| controller.name | string | `"higress-controller"` | |
@@ -98,6 +99,7 @@ The command removes all the Kubernetes components associated with the chart and
| gateway.httpsPort | int | `443` | |
| gateway.hub | string | `""` | |
| gateway.image | string | `"gateway"` | |
| gateway.imagePullPolicy | string | `""` | Specify image pull policy if default behavior isn't desired. Default behavior: latest images will be Always else IfNotPresent. |
| gateway.kind | string | `"Deployment"` | Use a `DaemonSet` or `Deployment` |
| gateway.labels | object | `{}` | Labels to apply to all resources |
| gateway.metrics.enabled | bool | `false` | If true, create PodMonitor or VMPodScrape for gateway |
@@ -159,10 +161,12 @@ The command removes all the Kubernetes components associated with the chart and
| global.caAddress | string | `""` | The customized CA address to retrieve certificates for the pods in the cluster. CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint. If not set explicitly, default to the Istio discovery address. |
| global.caName | string | `""` | The name of the CA for workload certificates. For example, when caName=GkeWorkloadCertificate, GKE workload certificates will be used as the certificates for workloads. The default value is "" and when caName="", the CA will be configured by other mechanisms (e.g., environmental variable CA_PROVIDER). |
| global.configCluster | bool | `false` | Configure a remote cluster as the config cluster for an external istiod. |
| global.createIngressClass | bool | `true` | Whether to create the IngressClass resource for global.ingressClass. Set this to false when reusing an existing IngressClass, for example during Nginx Ingress migration. |
| global.defaultPodDisruptionBudget | object | `{"enabled":false}` | enable pod disruption budget for the control plane, which is used to ensure Istio control plane components are gradually upgraded or recovered. |
| global.defaultResources | object | `{"requests":{"cpu":"10m"}}` | A minimal set of requested resources to applied to all deployments so that Horizontal Pod Autoscaler will be able to function (if set). Each component can overwrite these default values by adding its own resources block in the relevant section below and setting the desired resources values. |
| global.defaultUpstreamConcurrencyThreshold | int | `10000` | |
| global.disableAlpnH2 | bool | `false` | Whether to disable HTTP/2 in ALPN |
| global.enableAlphaGatewayAPI | bool | `false` | If true, Higress Controller will monitor Gateway API resources that have not reached v1 yet |
| global.enableDeltaXDS | bool | `true` | Whether to enable Istio delta xDS, default is false. |
| global.enableGatewayAPI | bool | `true` | If true, Higress Controller will monitor Gateway API resources as well |
| global.enableH3 | bool | `false` | |
@@ -177,8 +181,9 @@ The command removes all the Kubernetes components associated with the chart and
| global.enableSRDS | bool | `true` | |
| global.enableStatus | bool | `true` | If true, Higress Controller will update the status field of Ingress resources. When migrating from Nginx Ingress, in order to avoid status field of Ingress objects being overwritten, this parameter needs to be set to false, so Higress won't write the entry IP to the status field of the corresponding Ingress object. |
| global.externalIstiod | bool | `false` | Configure a remote cluster data plane controlled by an external istiod. When set to true, istiod is not deployed locally and only a subset of the other discovery charts are enabled. |
| global.gatewayClass | string | `"higress"` | GatewayClassName used by Higress to select Gateway API resources. The default value higress uses controllerName higress.io/gateway-controller. A custom value, for example higress-internal, uses controllerName higress.io/gateway-controller-higress-internal. |
| global.hostRDSMergeSubset | bool | `false` | |
| global.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | Default hub for Istio images. Releases are published to docker hub under 'istio' project. Dev builds from prow are on gcr.io |
| global.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com"` | Default hub (registry) for Higress images. For Higress deployments, images are pulled from: {hub}/higress/{image} For built-in plugins, images are pulled from: {hub}/{pluginNamespace}/{plugin-name} Change this to use a mirror registry closer to your deployment region for faster image pulls. |
| global.imagePullPolicy | string | `""` | Specify image pull policy if default behavior isn't desired. Default behavior: latest images will be Always else IfNotPresent. |
| global.imagePullSecrets | list | `[]` | ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace to use for pulling any images in pods that reference this ServiceAccount. For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing) ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects. Must be set for any cluster configured with private docker registry. |
| global.ingressClass | string | `"higress"` | IngressClass filters which ingress resources the higress controller watches. The default ingress class is higress. There are some special cases for special ingress class. 1. When the ingress class is set as nginx, the higress controller will watch ingress resources with the nginx ingress class or without any ingress class. 2. When the ingress class is set empty, the higress controller will watch all ingress resources in the k8s cluster. |
@@ -203,6 +208,7 @@ The command removes all the Kubernetes components associated with the chart and
| global.onlyPushRouteCluster | bool | `true` | |
| global.operatorManageWebhooks | bool | `false` | Configure whether Operator manages webhook configurations. The current behavior of Istiod is to manage its own webhook configurations. When this option is set as true, Istio Operator, instead of webhooks, manages the webhook configurations. When this option is set as false, webhooks manage their own webhook configurations. |
| global.pilotCertProvider | string | `"istiod"` | Configure the certificate provider for control plane communication. Currently, two providers are supported: "kubernetes" and "istiod". As some platforms may not have kubernetes signing APIs, Istiod is the default |
| global.pluginNamespace | string | `"plugins"` | Namespace for built-in plugin images. Default is "plugins". Used by higress-console to configure plugin image path. |
| global.priorityClassName | string | `""` | Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and system-node-critical, it is better to configure this in order to make sure your Istio pods will not be killed because of low priority class. Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass for more detail. |
| global.proxy.autoInject | string | `"enabled"` | This controls the 'policy' in the sidecar injector. |
| global.proxy.clusterDomain | string | `"cluster.local"` | CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value cluster domain. Default value is "cluster.local". |
@@ -252,13 +258,7 @@ The command removes all the Kubernetes components associated with the chart and
| meshConfig | object | `{"enablePrometheusMerge":true,"rootNamespace":null,"trustDomain":"cluster.local"}` | meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options |
| meshConfig.rootNamespace | string | `nil` | The namespace to treat as the administrative root namespace for Istio configuration. When processing a leaf namespace Istio will search for declarations in that namespace first and if none are found it will search in the root namespace. Any matching declaration found in the root namespace is processed as if it were declared in the leaf namespace. |
| meshConfig.trustDomain | string | `"cluster.local"` | The trust domain corresponds to the trust root of a system Refer to https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain |
| pilot.autoscaleEnabled | bool | `false` | |
| pilot.autoscaleMax | int | `5` | |
| pilot.autoscaleMin | int | `1` | |
| pilot.configMap | bool | `true` | Install the mesh config map, generated from values.yaml. If false, pilot wil use default values (by default) or user-supplied values. |
| pilot.configSource | object | `{"subscribedResources":[]}` | This is used to set the source of configuration for the associated address in configSource, if nothing is specified the default MCP is assumed. |
| pilot.cpu.targetAverageUtilization | int | `80` | |
| pilot.deploymentLabels | object | `{}` | Additional labels to apply to the deployment. |
| pilot.enableProtocolSniffingForInbound | bool | `true` | if protocol sniffing is enabled for inbound |
| pilot.enableProtocolSniffingForOutbound | bool | `true` | if protocol sniffing is enabled for outbound |
| pilot.env.PILOT_ENABLE_CROSS_CLUSTER_WORKLOAD_ENTRY | string | `"false"` | |
@@ -269,19 +269,13 @@ The command removes all the Kubernetes components associated with the chart and
| pilot.image | string | `"pilot"` | Can be a full hub/image:tag |
| pilot.jwksResolverExtraRootCA | string | `""` | You can use jwksResolverExtraRootCA to provide a root certificate in PEM format. This will then be trusted by pilot when resolving JWKS URIs. |
| pilot.keepaliveMaxServerConnectionAge | string | `"30m"` | The following is used to limit how long a sidecar can be connected to a pilot. It balances out load across pilot instances at the cost of increasing system churn. |
| pilot.nodeSelector | object | `{}` | |
| pilot.plugins | list | `[]` | |
| pilot.podAnnotations | object | `{}` | |
| pilot.podLabels | object | `{}` | Additional labels to apply on the pod level for monitoring and logging configuration. |
| pilot.replicaCount | int | `1` | |
| pilot.resources | object | `{"requests":{"cpu":"500m","memory":"2048Mi"}}` | Resources for a small pilot install |
| pilot.rollingMaxSurge | string | `"100%"` | |
| pilot.rollingMaxUnavailable | string | `"25%"` | |
| pilot.serviceAnnotations | object | `{}` | |
| pilot.tag | string | `""` | |
| pilot.traceSampling | float | `1` | |
| pluginServer.hub | string | `""` | |
| pluginServer.image | string | `"plugin-server"` | |
| pluginServer.imagePullPolicy | string | `""` | Specify image pull policy if default behavior isn't desired. Default behavior: latest images will be Always else IfNotPresent. |
| pluginServer.imagePullSecrets | list | `[]` | |
| pluginServer.labels | object | `{}` | |
| pluginServer.name | string | `"higress-plugin-server"` | |

View File

@@ -145,7 +145,9 @@ helm delete higress -n higress-system
| gateway.service.ports[1].protocol | string | `"TCP"` | 协议 |
| gateway.service.ports[1].targetPort | int | `443` | 靶向端口 |
| gateway.service.type | string | `"LoadBalancer"` | 服务类型 |
| global.createIngressClass | bool | `true` | 是否为 global.ingressClass 创建 IngressClass 资源。复用已有 IngressClass 时可设置为 false例如从 Nginx Ingress 迁移场景。 |
| global.disableAlpnH2 | bool | `false` | 设置是否禁用 ALPN 中的 http/2 |
| global.enableAlphaGatewayAPI | bool | `false` | 是否监听 alpha 阶段的 Gateway API 资源 |
| global.enableInferenceExtension | bool | `false` | 是否启用 Gateway API Inference Extension 支持 |
| ... | ... | ... | ... |

View File

@@ -110,6 +110,7 @@ type ServerArgs struct {
// 2. When the ingress class is set empty, the higress controller will watch all ingress
// resources in the k8s cluster.
IngressClass string
GatewayClass string
EnableStatus bool
WatchNamespace string
GrpcKeepAliveOptions *keepalive.Options
@@ -222,6 +223,7 @@ func (s *Server) initConfigController() error {
Enable: true,
ClusterId: s.RegistryOptions.KubeOptions.ClusterID,
IngressClass: s.IngressClass,
GatewayClass: s.GatewayClass,
WatchNamespace: s.WatchNamespace,
EnableStatus: s.EnableStatus,
SystemNamespace: higressconfig.PodNamespace,

View File

@@ -17,14 +17,12 @@ package cert
import (
"context"
"fmt"
"os"
"reflect"
"sync"
"github.com/caddyserver/certmagic"
"github.com/mholt/acmez"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
istiomodel "istio.io/istio/pilot/pkg/model"
"k8s.io/client-go/kubernetes"
)
@@ -60,11 +58,7 @@ func InitCertMgr(opts *Option, clientSet kubernetes.Interface, config *Config, X
var storage certmagic.Storage
storage, _ = NewConfigmapStorage(opts.Namespace, clientSet)
renewalWindowRatio := float64(config.RenewBeforeDays) / float64(RenewMaxDays)
logger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.DebugLevel,
))
logger := zap.L()
magicConfig := certmagic.Config{
RenewalWindowRatio: renewalWindowRatio,
Storage: storage,

View File

@@ -106,6 +106,7 @@ func getServerCommand() *cobra.Command {
serveCmd.PersistentFlags().StringVar(&serverArgs.GatewaySelectorValue, "gatewaySelectorValue", "higress-system-higress-gateway", "gateway resource selector label value")
serveCmd.PersistentFlags().BoolVar(&serverArgs.EnableStatus, "enableStatus", true, "enable the ingress status syncer which use to update the ip in ingress's status")
serveCmd.PersistentFlags().StringVar(&serverArgs.IngressClass, "ingressClass", innerconstants.DefaultIngressClass, "if not empty, only watch the ingresses have the specified class, otherwise watch all ingresses")
serveCmd.PersistentFlags().StringVar(&serverArgs.GatewayClass, "gatewayClass", innerconstants.DefaultGatewayClass, "if not empty, only process Gateway API resources that belong to the specified GatewayClass")
serveCmd.PersistentFlags().StringVar(&serverArgs.WatchNamespace, "watchNamespace", "", "if not empty, only wath the ingresses in the specified namespace, otherwise watch in all namespacees")
serveCmd.PersistentFlags().BoolVar(&serverArgs.Debug, "debug", serverArgs.Debug, "if true, enables more debug http api")
serveCmd.PersistentFlags().StringVar(&serverArgs.HttpAddress, "httpAddress", serverArgs.HttpAddress, "the http address")

View File

@@ -46,6 +46,8 @@ import (
"istio.io/istio/pkg/log"
"istio.io/istio/pkg/util/sets"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
listersv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
@@ -438,6 +440,7 @@ func (m *IngressConfig) convertGateways(configs []common.WrapperConfig) []config
if err != nil {
IngressLog.Errorf("Get higress https configmap err %v", err)
}
m.preparePassthroughTLSHostOwners(&convertOptions, configs)
for idx := range configs {
cfg := configs[idx]
clusterId := common.GetClusterId(cfg.Config.Annotations)
@@ -504,6 +507,8 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
}
}
m.preparePassthroughTLSHostOwners(&convertOptions, configs)
// convert http route
for idx := range configs {
cfg := configs[idx]
@@ -570,13 +575,8 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
m.ingressRouteCache = convertOptions.IngressRouteCache.Extract()
m.mutex.Unlock()
// Convert http route to virtual service
out := make([]config.Config, 0, len(convertOptions.HTTPRoutes))
for host, routes := range convertOptions.HTTPRoutes {
if len(routes) == 0 {
continue
}
out := make([]config.Config, 0, len(convertOptions.VirtualServices))
for host, wrapperVS := range convertOptions.VirtualServices {
cleanHost := common.CleanHost(host)
// namespace/name, name format: (istio cluster id)-host
gateways := []string{
@@ -585,13 +585,10 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
common.CreateConvertedName(constants.IstioIngressGatewayName, cleanHost),
}
wrapperVS, exist := convertOptions.VirtualServices[host]
if !exist {
IngressLog.Warnf("virtual service for host %s does not exist.", host)
}
vs := wrapperVS.VirtualService
vs.Gateways = gateways
routes := convertOptions.HTTPRoutes[host]
// Sort, exact -> prefix -> regex
common.SortHTTPRoutes(routes)
@@ -599,14 +596,18 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
vs.Http = append(vs.Http, route.HTTPRoute)
}
firstRoute := routes[0]
if len(vs.Http) == 0 && len(vs.Tls) == 0 {
continue
}
vsName, clusterId := virtualServiceNameAndClusterID(cleanHost, wrapperVS, routes)
out = append(out, config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, firstRoute.WrapperConfig.Config.Namespace, firstRoute.WrapperConfig.Config.Name, cleanHost),
Name: vsName,
Namespace: m.namespace,
Annotations: map[string]string{
common.ClusterIdAnnotation: firstRoute.ClusterId.String(),
common.ClusterIdAnnotation: clusterId.String(),
},
},
Spec: vs,
@@ -625,6 +626,129 @@ func (m *IngressConfig) convertVirtualService(configs []common.WrapperConfig) []
return out
}
func virtualServiceNameAndClusterID(cleanHost string, wrapperVS *common.WrapperVirtualService, routes []*common.WrapperHTTPRoute) (string, cluster.ID) {
if len(routes) > 0 {
firstRoute := routes[0]
return common.CreateConvertedName(constants.IstioIngressGatewayName, firstRoute.WrapperConfig.Config.Namespace, firstRoute.WrapperConfig.Config.Name, cleanHost), firstRoute.ClusterId
}
cfg := wrapperVS.WrapperConfig.Config
return common.CreateConvertedName(constants.IstioIngressGatewayName, cfg.Namespace, cfg.Name, cleanHost), common.GetClusterId(cfg.Annotations)
}
func (m *IngressConfig) preparePassthroughTLSHostOwners(convertOptions *common.ConvertOptions, configs []common.WrapperConfig) {
if convertOptions.PassthroughTLSHostOwners == nil {
convertOptions.PassthroughTLSHostOwners = map[string]*config.Config{}
}
// ingress-nginx enables SSL passthrough at host level when any ingress for the host has the
// annotation, then uses the first root path as the passthrough backend.
passthroughHosts := map[string]struct{}{}
firstRootPathHostOwners := map[string]*config.Config{}
for idx := range configs {
cfg := configs[idx]
if cfg.AnnotationsConfig.IsCanary() {
continue
}
if cfg.AnnotationsConfig.IsSSLPassthrough() {
for _, host := range ingressRuleHosts(cfg.Config.Spec) {
passthroughHosts[host] = struct{}{}
}
}
for _, host := range ingressRootPathHosts(cfg.Config.Spec) {
if _, exist := firstRootPathHostOwners[host]; exist {
continue
}
firstRootPathHostOwners[host] = cfg.Config
}
}
for host := range passthroughHosts {
if owner := firstRootPathHostOwners[host]; owner != nil {
convertOptions.PassthroughTLSHostOwners[host] = owner
}
}
}
func ingressRuleHosts(spec config.Spec) []string {
switch ingressSpec := spec.(type) {
case networkingv1.IngressSpec:
return ingressV1RuleHosts(ingressSpec.Rules)
case networkingv1beta1.IngressSpec:
return ingressV1Beta1RuleHosts(ingressSpec.Rules)
default:
return nil
}
}
func ingressRootPathHosts(spec config.Spec) []string {
switch ingressSpec := spec.(type) {
case networkingv1.IngressSpec:
return ingressV1RootPathHosts(ingressSpec.Rules)
case networkingv1beta1.IngressSpec:
return ingressV1Beta1RootPathHosts(ingressSpec.Rules)
default:
return nil
}
}
func ingressV1RuleHosts(rules []networkingv1.IngressRule) []string {
out := make([]string, 0, len(rules))
for _, rule := range rules {
out = append(out, rule.Host)
}
return out
}
func ingressV1Beta1RuleHosts(rules []networkingv1beta1.IngressRule) []string {
out := make([]string, 0, len(rules))
for _, rule := range rules {
out = append(out, rule.Host)
}
return out
}
func ingressV1RootPathHosts(rules []networkingv1.IngressRule) []string {
out := make([]string, 0, len(rules))
for _, rule := range rules {
if rule.HTTP == nil || !hasV1RootHTTPIngressPath(rule.HTTP.Paths) {
continue
}
out = append(out, rule.Host)
}
return out
}
func ingressV1Beta1RootPathHosts(rules []networkingv1beta1.IngressRule) []string {
out := make([]string, 0, len(rules))
for _, rule := range rules {
if rule.HTTP == nil || !hasV1Beta1RootHTTPIngressPath(rule.HTTP.Paths) {
continue
}
out = append(out, rule.Host)
}
return out
}
func hasV1RootHTTPIngressPath(paths []networkingv1.HTTPIngressPath) bool {
for _, path := range paths {
if path.Path == "" || path.Path == "/" {
return true
}
}
return false
}
func hasV1Beta1RootHTTPIngressPath(paths []networkingv1beta1.HTTPIngressPath) bool {
for _, path := range paths {
if path.Path == "" || path.Path == "/" {
return true
}
}
return false
}
func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions) {
var envoyFilters []config.Config
mappings := map[string]*common.Rule{}
@@ -1759,7 +1883,7 @@ func constructProxyEnvoyFilters(proxyWrappers map[string]*common.ProxyWrapper, s
continue
}
if !proxyConfig.UpstreamProtocol.IsSupportedByProxy() {
IngressLog.Warnf("Proxy %s does not support upstream protocol %s, skipping EnvoyFilter construction for service %s")
IngressLog.Warnf("Proxy %s does not support upstream protocol %s, skipping EnvoyFilter construction for service %s", proxyConfig.ProxyName, proxyConfig.UpstreamProtocol, serviceWrapper.ServiceName)
continue
}
if proxyWrapper.EnvoyFilter == nil {

View File

@@ -23,6 +23,7 @@ import (
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/cluster"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/constants"
"istio.io/istio/pkg/config/schema/gvk"
"istio.io/istio/pkg/config/xds"
ingress "k8s.io/api/networking/v1"
@@ -109,6 +110,405 @@ func TestNormalizeWeightedCluster(t *testing.T) {
}
}
func TestVirtualServiceNameAndClusterID(t *testing.T) {
cleanHost := common.CleanHost("example.com")
wrapperVS := &common.WrapperVirtualService{
WrapperConfig: &common.WrapperConfig{
Config: &config.Config{
Meta: config.Meta{
Namespace: "tls-ns",
Name: "tls-ingress",
Annotations: map[string]string{
common.ClusterIdAnnotation: "tls-cluster",
},
},
},
},
}
routes := []*common.WrapperHTTPRoute{
{
WrapperConfig: &common.WrapperConfig{
Config: &config.Config{
Meta: config.Meta{
Namespace: "http-ns",
Name: "http-ingress",
},
},
},
ClusterId: "http-cluster",
},
}
name, clusterID := virtualServiceNameAndClusterID(cleanHost, wrapperVS, routes)
if name != common.CreateConvertedName(constants.IstioIngressGatewayName, "http-ns", "http-ingress", cleanHost) {
t.Fatalf("http-backed virtual service name mismatch: %s", name)
}
if clusterID != "http-cluster" {
t.Fatalf("http-backed cluster id mismatch: %s", clusterID)
}
name, clusterID = virtualServiceNameAndClusterID(cleanHost, wrapperVS, nil)
if name != common.CreateConvertedName(constants.IstioIngressGatewayName, "tls-ns", "tls-ingress", cleanHost) {
t.Fatalf("tls-only virtual service name mismatch: %s", name)
}
if clusterID != "tls-cluster" {
t.Fatalf("tls-only cluster id mismatch: %s", clusterID)
}
}
func TestPreparePassthroughTLSHostOwnersRequiresPassthroughHost(t *testing.T) {
m := &IngressConfig{}
configs := []common.WrapperConfig{
{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "plain-root",
},
Spec: ingress.IngressSpec{
Rules: []ingress.IngressRule{
{
Host: "example.com",
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{Path: "/"},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{},
},
{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "plain-root-duplicate",
},
Spec: ingress.IngressSpec{
Rules: []ingress.IngressRule{
{
Host: "example.com",
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{Path: "/"},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{},
},
}
options := &common.ConvertOptions{}
m.preparePassthroughTLSHostOwners(options, configs)
if len(options.PassthroughTLSHostOwners) != 0 {
t.Fatalf("unexpected ssl passthrough owners: %+v", options.PassthroughTLSHostOwners)
}
}
func TestPreparePassthroughTLSHostOwnersUsesFirstRootPathOwner(t *testing.T) {
m := &IngressConfig{}
configs := []common.WrapperConfig{
{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "plain-root",
},
Spec: ingress.IngressSpec{
Rules: []ingress.IngressRule{
{
Host: "example.com",
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{Path: "/"},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{},
},
{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "passthrough-non-root",
},
Spec: ingress.IngressSpec{
Rules: []ingress.IngressRule{
{
Host: "example.com",
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{Path: "/api"},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
},
},
}
options := &common.ConvertOptions{}
m.preparePassthroughTLSHostOwners(options, configs)
if !common.IsPassthroughTLSHostOwner(options, configs[0].Config, "example.com") {
t.Fatal("first root ingress was not recorded as passthrough owner")
}
if !common.HasPassthroughTLSHostOwner(options, configs[0].Config) {
t.Fatal("first root ingress was not found as passthrough owner")
}
}
func TestPreparePassthroughTLSHostOwnersIgnoresHTTPOnlyIngressForHTTPSFallback(t *testing.T) {
m := &IngressConfig{}
configs := []common.WrapperConfig{
{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "http-only",
},
Spec: ingress.IngressSpec{
Rules: []ingress.IngressRule{
{
Host: "example.com",
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{Path: "/api"},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{},
},
{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "tls-ingress",
},
Spec: ingress.IngressSpec{
TLS: []ingress.IngressTLS{
{
Hosts: []string{"example.com"},
SecretName: "example-com",
},
},
Rules: []ingress.IngressRule{
{
Host: "example.com",
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{Path: "/app"},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{},
},
}
options := &common.ConvertOptions{}
m.preparePassthroughTLSHostOwners(options, configs)
if len(options.PassthroughTLSHostOwners) != 0 {
t.Fatalf("unexpected ssl passthrough owners: %+v", options.PassthroughTLSHostOwners)
}
}
func TestConvertGatewaysHonorsFirstRootPathSSLPassthroughOwner(t *testing.T) {
fake := kube.NewFakeClient()
options := common.Options{
Enable: true,
ClusterId: "ingress-v1",
RawClusterId: "ingress-v1__",
GatewayHttpPort: 80,
GatewayHttpsPort: 443,
}
ingressController := controllerv1.NewController(fake, fake, options, nil)
m := NewIngressConfig(fake, nil, "wakanda", options)
m.remoteIngressControllers = map[cluster.ID]common.IngressController{
"ingress-v1": ingressController,
}
configs := []common.WrapperConfig{
{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "tls-non-root",
Annotations: map[string]string{
common.ClusterIdAnnotation: "ingress-v1",
},
},
Spec: ingress.IngressSpec{
TLS: []ingress.IngressTLS{
{
Hosts: []string{"example.com"},
SecretName: "example-com",
},
},
Rules: []ingress.IngressRule{
{
Host: "example.com",
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{Path: "/api"},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{},
},
{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "passthrough-root",
Annotations: map[string]string{
common.ClusterIdAnnotation: "ingress-v1",
},
},
Spec: ingress.IngressSpec{
Rules: []ingress.IngressRule{
{
Host: "example.com",
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{Path: "/"},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{
SSLPassthrough: &annotations.SSLPassthroughConfig{Enabled: true},
},
},
}
result := m.convertGateways(configs)
if len(result) != 1 {
t.Fatalf("gateway count mismatch, want 1, got %d", len(result))
}
gateway := result[0].Spec.(*networking.Gateway)
if len(gateway.Servers) != 2 {
t.Fatalf("server count mismatch, want 2, got %d", len(gateway.Servers))
}
tlsServer := gateway.Servers[1]
if tlsServer.Port.Protocol != "TLS" {
t.Fatalf("tls server protocol mismatch, want TLS, got %s", tlsServer.Port.Protocol)
}
if tlsServer.Tls.GetMode() != networking.ServerTLSSettings_PASSTHROUGH {
t.Fatalf("tls mode mismatch, want PASSTHROUGH, got %s", tlsServer.Tls.GetMode())
}
}
func TestConvertGatewaysUsesFirstRootOwnerWhenLaterIngressEnablesSSLPassthrough(t *testing.T) {
fake := kube.NewFakeClient()
options := common.Options{
Enable: true,
ClusterId: "ingress-v1",
RawClusterId: "ingress-v1__",
GatewayHttpPort: 80,
GatewayHttpsPort: 443,
}
ingressController := controllerv1.NewController(fake, fake, options, nil)
m := NewIngressConfig(fake, nil, "wakanda", options)
m.remoteIngressControllers = map[cluster.ID]common.IngressController{
"ingress-v1": ingressController,
}
configs := []common.WrapperConfig{
ingressV1Wrapper("root", "example.com", "/", false),
ingressV1Wrapper("passthrough", "example.com", "/passthrough", true),
}
result := m.convertGateways(configs)
if len(result) != 1 {
t.Fatalf("gateway count mismatch, want 1, got %d", len(result))
}
gateway := result[0].Spec.(*networking.Gateway)
if len(gateway.Servers) != 2 {
t.Fatalf("server count mismatch, want 2, got %d", len(gateway.Servers))
}
tlsServer := gateway.Servers[1]
if tlsServer.Port.Protocol != "TLS" {
t.Fatalf("tls server protocol mismatch, want TLS, got %s", tlsServer.Port.Protocol)
}
if tlsServer.Tls.GetMode() != networking.ServerTLSSettings_PASSTHROUGH {
t.Fatalf("tls mode mismatch, want PASSTHROUGH, got %s", tlsServer.Tls.GetMode())
}
}
func TestConvertVirtualServiceUsesFirstRootOwnerWhenLaterIngressEnablesSSLPassthrough(t *testing.T) {
fake := kube.NewFakeClient()
options := common.Options{
Enable: true,
ClusterId: "ingress-v1",
RawClusterId: "ingress-v1__",
GatewayHttpPort: 80,
GatewayHttpsPort: 443,
}
ingressController := controllerv1.NewController(fake, fake, options, nil)
m := NewIngressConfig(fake, nil, "wakanda", options)
m.remoteIngressControllers = map[cluster.ID]common.IngressController{
"ingress-v1": ingressController,
}
configs := []common.WrapperConfig{
ingressV1Wrapper("root", "example.com", "/", false),
ingressV1Wrapper("passthrough", "example.com", "/passthrough", true),
}
result := m.convertVirtualService(configs)
if len(result) != 1 {
t.Fatalf("virtual service count mismatch, want 1, got %d", len(result))
}
vs := result[0].Spec.(*networking.VirtualService)
if len(vs.Tls) != 1 {
t.Fatalf("tls route count mismatch, want 1, got %d", len(vs.Tls))
}
if got := vs.Tls[0].Route[0].Destination.Host; got != "root.default.svc.cluster.local" {
t.Fatalf("destination host mismatch, want root.default.svc.cluster.local, got %s", got)
}
}
func TestConvertGatewaysForIngress(t *testing.T) {
fake := kube.NewFakeClient()
v1Beta1Options := common.Options{
@@ -616,3 +1016,46 @@ func TestConstructBasicAuthEnvoyFilter(t *testing.T) {
target := proto.Clone(pb).(*httppb.HttpFilter)
t.Log(target)
}
func ingressV1Wrapper(name, host, path string, sslPassthrough bool) common.WrapperConfig {
wrapper := common.WrapperConfig{
Config: &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: name,
Annotations: map[string]string{
common.ClusterIdAnnotation: "ingress-v1",
},
},
Spec: ingress.IngressSpec{
Rules: []ingress.IngressRule{
{
Host: host,
IngressRuleValue: ingress.IngressRuleValue{
HTTP: &ingress.HTTPIngressRuleValue{
Paths: []ingress.HTTPIngressPath{
{
Path: path,
Backend: ingress.IngressBackend{
Service: &ingress.IngressServiceBackend{
Name: name,
Port: ingress.ServiceBackendPort{Number: 443},
},
},
},
},
},
},
},
},
},
},
AnnotationsConfig: &annotations.Ingress{
Match: &annotations.MatchConfig{},
},
}
if sslPassthrough {
wrapper.AnnotationsConfig.SSLPassthrough = &annotations.SSLPassthroughConfig{Enabled: true}
}
return wrapper
}

View File

@@ -53,7 +53,7 @@ func (p *TemplateProcessor) ProcessConfig(cfg *config.Config) error {
configStr := string(jsonBytes)
// Find all value references in format:
// ${type.name.key} or ${type.namespace/name.key}
valueRegex := regexp.MustCompile(`\$\{([^.}]+)\.(?:([^/]+)/)?([^.}]+)\.([^}]+)\}`)
valueRegex := regexp.MustCompile(`\$\{([^.}/]+)\.(?:([^/}]+)/)?([^.}/]+)\.([^}]+)\}`)
matches := valueRegex.FindAllStringSubmatch(configStr, -1)
// If there are no value references, return immediately
if len(matches) == 0 {

View File

@@ -114,6 +114,66 @@ func TestTemplateProcessor_ProcessConfig(t *testing.T) {
},
expectError: false,
},
{
name: "config with default and non-default namespaces (default first)",
wasmPlugin: &extensions.WasmPlugin{
PluginName: "test-plugin",
PluginConfig: makeStructValue(t, map[string]interface{}{
"a1": map[string]interface{}{
"type": "${secret.auth-secret.auth_config.type}",
"credentials": "${secret.auth-secret.auth_config.credentials}",
},
"a2": map[string]interface{}{
"timeout": "${secret.default/test-secret.plugin_conf.timeout}",
"max_retries": "${secret.default/test-secret.plugin_conf.max_retries}",
},
}),
},
expected: &extensions.WasmPlugin{
PluginName: "test-plugin",
PluginConfig: makeStructValue(t, map[string]interface{}{
"a1": map[string]interface{}{
"type": "basic",
"credentials": "base64-encoded",
},
"a2": map[string]interface{}{
"timeout": "5000",
"max_retries": "3",
},
}),
},
expectError: false,
},
{
name: "config with default and non-default namespaces (non-default first)",
wasmPlugin: &extensions.WasmPlugin{
PluginName: "test-plugin",
PluginConfig: makeStructValue(t, map[string]interface{}{
"a1": map[string]interface{}{
"timeout": "${secret.default/test-secret.plugin_conf.timeout}",
"max_retries": "${secret.default/test-secret.plugin_conf.max_retries}",
},
"a2": map[string]interface{}{
"type": "${secret.auth-secret.auth_config.type}",
"credentials": "${secret.auth-secret.auth_config.credentials}",
},
}),
},
expected: &extensions.WasmPlugin{
PluginName: "test-plugin",
PluginConfig: makeStructValue(t, map[string]interface{}{
"a1": map[string]interface{}{
"timeout": "5000",
"max_retries": "3",
},
"a2": map[string]interface{}{
"type": "basic",
"credentials": "base64-encoded",
},
}),
},
expectError: false,
},
{
name: "non-existent secret",
wasmPlugin: &extensions.WasmPlugin{

View File

@@ -57,6 +57,8 @@ type Ingress struct {
DownstreamTLS *DownstreamTLSConfig
SSLPassthrough *SSLPassthroughConfig
Canary *CanaryConfig
IPAccessControl *IPAccessControlConfig
@@ -115,6 +117,10 @@ func (i *Ingress) IsCanary() bool {
return i.Canary.Enabled
}
func (i *Ingress) IsSSLPassthrough() bool {
return i.SSLPassthrough != nil && i.SSLPassthrough.Enabled
}
// CanaryKind return byHeader, byWeight
func (i *Ingress) CanaryKind() (bool, bool) {
if !i.IsCanary() {
@@ -157,6 +163,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
canary{},
cors{},
downstreamTLS{},
sslPassthrough{},
redirect{},
rewrite{},
upstreamTLS{},

View File

@@ -106,6 +106,9 @@ func (d downstreamTLS) ApplyGateway(gateway *networking.Gateway, config *Ingress
downstreamTLSConfig := config.DownstreamTLS
for _, server := range gateway.Servers {
if gatewaytool.IsTLSServer(server) {
if server.Tls != nil && server.Tls.Mode == networking.ServerTLSSettings_PASSTHROUGH {
continue
}
if downstreamTLSConfig.CASecretName.Name != "" {
serverCert := extraSecret(server.Tls.CredentialName)
if downstreamTLSConfig.CASecretName.Namespace != serverCert.Namespace ||

View File

@@ -269,6 +269,40 @@ func TestApplyGateway(t *testing.T) {
},
},
},
{
name: "skip passthrough server",
input: &networking.Gateway{
Servers: []*networking.Server{
{
Port: &networking.Port{
Protocol: "TLS",
},
Tls: &networking.ServerTLSSettings{
Mode: networking.ServerTLSSettings_PASSTHROUGH,
},
},
},
},
config: &Ingress{
DownstreamTLS: &DownstreamTLSConfig{
CipherSuites: []string{"ECDHE-RSA-AES256-GCM-SHA384"},
MinVersion: "TLSv1.2",
MaxVersion: "TLSv1.3",
},
},
expect: &networking.Gateway{
Servers: []*networking.Server{
{
Port: &networking.Port{
Protocol: "TLS",
},
Tls: &networking.ServerTLSSettings{
Mode: networking.ServerTLSSettings_PASSTHROUGH,
},
},
},
},
},
}
for _, tc := range testCases {

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package annotations
const sslPassthroughAnnotation = "ssl-passthrough"
var _ Parser = &sslPassthrough{}
type SSLPassthroughConfig struct {
Enabled bool
}
type sslPassthrough struct{}
func (s sslPassthrough) Parse(annotations Annotations, config *Ingress, _ *GlobalContext) error {
enabled, err := annotations.ParseBoolASAP(sslPassthroughAnnotation)
if err != nil {
return nil
}
config.SSLPassthrough = &SSLPassthroughConfig{Enabled: enabled}
return nil
}

View File

@@ -0,0 +1,112 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package annotations
import "testing"
func TestSSLPassthroughParse(t *testing.T) {
testCases := []struct {
name string
input Annotations
enabled bool
exists bool
}{
{
name: "missing",
input: Annotations{},
},
{
name: "enabled by nginx annotation",
input: Annotations{
buildNginxAnnotationKey(sslPassthroughAnnotation): "true",
},
enabled: true,
exists: true,
},
{
name: "enabled by higress annotation",
input: Annotations{
buildHigressAnnotationKey(sslPassthroughAnnotation): "true",
},
enabled: true,
exists: true,
},
{
name: "disabled by nginx annotation",
input: Annotations{
buildNginxAnnotationKey(sslPassthroughAnnotation): "false",
},
exists: true,
},
{
name: "disabled by higress annotation",
input: Annotations{
buildHigressAnnotationKey(sslPassthroughAnnotation): "false",
},
exists: true,
},
}
parser := sslPassthrough{}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
config := &Ingress{}
if err := parser.Parse(tc.input, config, nil); err != nil {
t.Fatalf("Parse() error = %v", err)
}
if tc.exists && config.SSLPassthrough == nil {
t.Fatal("expected ssl passthrough config")
}
if !tc.exists && config.SSLPassthrough != nil {
t.Fatal("unexpected ssl passthrough config")
}
if tc.exists && config.SSLPassthrough.Enabled != tc.enabled {
t.Fatalf("enabled mismatch, want %v, got %v", tc.enabled, config.SSLPassthrough.Enabled)
}
})
}
}
func TestSSLPassthroughDoesNotSetUpstreamTLS(t *testing.T) {
parser := sslPassthrough{}
config := &Ingress{}
err := parser.Parse(Annotations{
buildNginxAnnotationKey(sslPassthroughAnnotation): "true",
}, config, nil)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if config.UpstreamTLS != nil {
t.Fatal("unexpected upstream tls config")
}
}
func TestSSLPassthroughKeepsExplicitBackendProtocol(t *testing.T) {
manager := NewAnnotationHandlerManager()
config := &Ingress{}
err := manager.Parse(Annotations{
buildNginxAnnotationKey(sslPassthroughAnnotation): "true",
buildNginxAnnotationKey(backendProtocol): "HTTPS",
}, config, nil)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if config.UpstreamTLS == nil {
t.Fatal("expected upstream tls config")
}
if config.UpstreamTLS.BackendProtocol != "HTTPS" {
t.Fatalf("backend protocol mismatch, want HTTPS, got %s", config.UpstreamTLS.BackendProtocol)
}
}

View File

@@ -15,6 +15,7 @@
package common
import (
"strconv"
"strings"
"time"
@@ -23,6 +24,7 @@ import (
"istio.io/istio/pkg/cluster"
"istio.io/istio/pkg/config"
gatewaytool "istio.io/istio/pkg/config/gateway"
"istio.io/istio/pkg/config/protocol"
listerv1 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
@@ -78,6 +80,20 @@ func (w *WrapperGateway) IsHTTPS() bool {
return false
}
func CreateSSLPassthroughServer(host string, port uint32, clusterId cluster.ID) *networking.Server {
return &networking.Server{
Port: &networking.Port{
Number: port,
Protocol: string(protocol.TLS),
Name: CreateConvertedName("tls-"+strconv.FormatUint(uint64(port), 10)+"-ingress", clusterId.String()),
},
Hosts: []string{WildcardHost(host)},
Tls: &networking.ServerTLSSettings{
Mode: networking.ServerTLSSettings_PASSTHROUGH,
},
}
}
type WrapperHTTPRoute struct {
HTTPRoute *networking.HTTPRoute
WrapperConfig *WrapperConfig
@@ -111,6 +127,50 @@ type WrapperVirtualService struct {
AppRoot string
}
func (w *WrapperVirtualService) HasTLSRouteForHost(host string) bool {
if w == nil || w.VirtualService == nil {
return false
}
host = WildcardHost(host)
for _, route := range w.VirtualService.Tls {
for _, match := range route.Match {
for _, sniHost := range match.SniHosts {
if WildcardHost(sniHost) == host {
return true
}
}
}
}
return false
}
func NewWrapperVirtualService(host string, wrapper *WrapperConfig) *WrapperVirtualService {
return &WrapperVirtualService{
VirtualService: &networking.VirtualService{
Hosts: []string{WildcardHost(host)},
},
WrapperConfig: wrapper,
}
}
func CreateTLSRoute(host string, routeDestination []*networking.RouteDestination) *networking.TLSRoute {
return &networking.TLSRoute{
Match: []*networking.TLSMatchAttributes{
{
SniHosts: []string{WildcardHost(host)},
},
},
Route: routeDestination,
}
}
func WildcardHost(host string) string {
if host == "" {
return "*"
}
return host
}
type WrapperTrafficPolicy struct {
TrafficPolicy *networking.TrafficPolicy
PortTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy

View File

@@ -145,6 +145,41 @@ func (i *IngressDomainCache) Extract() model.IngressDomainCollection {
}
}
func SameConfig(left *config.Config, right *config.Config) bool {
if left == nil || right == nil {
return left == right
}
return GetClusterId(left.Annotations) == GetClusterId(right.Annotations) &&
left.Namespace == right.Namespace &&
left.Name == right.Name
}
func IsPassthroughTLSHostOwner(convertOptions *ConvertOptions, cfg *config.Config, host string) bool {
if convertOptions == nil || convertOptions.PassthroughTLSHostOwners == nil {
return true
}
return SameConfig(convertOptions.PassthroughTLSHostOwners[host], cfg)
}
func PassthroughTLSHostOwner(convertOptions *ConvertOptions, host string) *config.Config {
if convertOptions == nil || len(convertOptions.PassthroughTLSHostOwners) == 0 {
return nil
}
return convertOptions.PassthroughTLSHostOwners[host]
}
func HasPassthroughTLSHostOwner(convertOptions *ConvertOptions, cfg *config.Config) bool {
if convertOptions == nil || len(convertOptions.PassthroughTLSHostOwners) == 0 {
return false
}
for _, owner := range convertOptions.PassthroughTLSHostOwners {
if SameConfig(owner, cfg) {
return true
}
}
return false
}
type ConvertOptions struct {
HostWithRule2Ingress map[string]*config.Config
@@ -167,6 +202,9 @@ type ConvertOptions struct {
CanaryIngresses []*WrapperConfig
// Host to the first root-path ingress owner for hosts that have TLS passthrough enabled.
PassthroughTLSHostOwners map[string]*config.Config
Service2TrafficPolicy map[ServiceKey]*WrapperTrafficPolicy
ServiceWrappers map[string]*ServiceWrapper

View File

@@ -18,10 +18,48 @@ import (
"testing"
"github.com/stretchr/testify/assert"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/config"
)
func TestWildcardHostForSSLPassthrough(t *testing.T) {
server := CreateSSLPassthroughServer("", 443, "")
assert.Equal(t, []string{"*"}, server.Hosts)
vs := NewWrapperVirtualService("", &WrapperConfig{})
assert.Equal(t, []string{"*"}, vs.VirtualService.Hosts)
route := CreateTLSRoute("", []*networking.RouteDestination{{Weight: 100}})
assert.Equal(t, []string{"*"}, route.Match[0].SniHosts)
vs.VirtualService.Tls = append(vs.VirtualService.Tls, route)
assert.True(t, vs.HasTLSRouteForHost(""))
}
func TestPassthroughTLSHostOwnerNilMapAllowsStandaloneConversion(t *testing.T) {
cfg := &config.Config{
Meta: config.Meta{
Namespace: "default",
Name: "tls-passthrough",
},
}
// A nil owner map means the caller did not prepare ownership from the full ingress snapshot.
assert.True(t, IsPassthroughTLSHostOwner(&ConvertOptions{}, cfg, "example.com"))
assert.Nil(t, PassthroughTLSHostOwner(&ConvertOptions{}, "example.com"))
// A non-nil owner map means ownership has been prepared and missing hosts have no owner.
options := &ConvertOptions{
PassthroughTLSHostOwners: map[string]*config.Config{},
}
assert.False(t, IsPassthroughTLSHostOwner(options, cfg, "example.com"))
assert.Nil(t, PassthroughTLSHostOwner(options, "example.com"))
options.PassthroughTLSHostOwners["example.com"] = cfg
assert.True(t, IsPassthroughTLSHostOwner(options, cfg, "example.com"))
assert.Equal(t, cfg, PassthroughTLSHostOwner(options, "example.com"))
}
func TestIngressDomainCache(t *testing.T) {
cache := NewIngressDomainCache()
assert.NotNil(t, cache)

View File

@@ -364,6 +364,8 @@ func getLoadBalancerIp(svc *v1.Service) []string {
hostName := strings.TrimSuffix(ingress.Hostname, SvcHostNameSuffix)
if net.ParseIP(hostName) != nil {
out = append(out, hostName)
} else {
out = append(out, ingress.Hostname)
}
}
}
@@ -389,7 +391,8 @@ func getSvcIpList(svcList []*v1.Service) []string {
func SortLbIngressList(lbi []v1.LoadBalancerIngress) func(int, int) bool {
return func(i int, j int) bool {
return lbi[i].IP < lbi[j].IP
return loadBalancerIngressAddress(lbi[i].IP, lbi[i].Hostname) <
loadBalancerIngressAddress(lbi[j].IP, lbi[j].Hostname)
}
}
@@ -397,7 +400,11 @@ func GetLbStatusList(svcList []*v1.Service) []v1.LoadBalancerIngress {
svcIpList := getSvcIpList(svcList)
lbi := make([]v1.LoadBalancerIngress, 0, len(svcIpList))
for _, ep := range svcIpList {
lbi = append(lbi, v1.LoadBalancerIngress{IP: ep})
if net.ParseIP(ep) != nil {
lbi = append(lbi, v1.LoadBalancerIngress{IP: ep})
} else {
lbi = append(lbi, v1.LoadBalancerIngress{Hostname: ep})
}
}
sort.SliceStable(lbi, SortLbIngressList(lbi))
@@ -406,7 +413,8 @@ func GetLbStatusList(svcList []*v1.Service) []v1.LoadBalancerIngress {
func SortLbIngressListV1(lbi []networkingv1.IngressLoadBalancerIngress) func(int, int) bool {
return func(i int, j int) bool {
return lbi[i].IP < lbi[j].IP
return loadBalancerIngressAddress(lbi[i].IP, lbi[i].Hostname) <
loadBalancerIngressAddress(lbi[j].IP, lbi[j].Hostname)
}
}
@@ -414,7 +422,11 @@ func GetLbStatusListV1(svcList []*v1.Service) []networkingv1.IngressLoadBalancer
svcIpList := getSvcIpList(svcList)
lbi := make([]networkingv1.IngressLoadBalancerIngress, 0, len(svcIpList))
for _, ep := range svcIpList {
lbi = append(lbi, networkingv1.IngressLoadBalancerIngress{IP: ep})
if net.ParseIP(ep) != nil {
lbi = append(lbi, networkingv1.IngressLoadBalancerIngress{IP: ep})
} else {
lbi = append(lbi, networkingv1.IngressLoadBalancerIngress{Hostname: ep})
}
}
sort.SliceStable(lbi, SortLbIngressListV1(lbi))
@@ -423,7 +435,8 @@ func GetLbStatusListV1(svcList []*v1.Service) []networkingv1.IngressLoadBalancer
func SortLbIngressListV1Beta1(lbi []networkingv1beta1.IngressLoadBalancerIngress) func(int, int) bool {
return func(i int, j int) bool {
return lbi[i].IP < lbi[j].IP
return loadBalancerIngressAddress(lbi[i].IP, lbi[i].Hostname) <
loadBalancerIngressAddress(lbi[j].IP, lbi[j].Hostname)
}
}
@@ -431,9 +444,20 @@ func GetLbStatusListV1Beta1(svcList []*v1.Service) []networkingv1beta1.IngressLo
svcIpList := getSvcIpList(svcList)
lbi := make([]networkingv1beta1.IngressLoadBalancerIngress, 0, len(svcIpList))
for _, ep := range svcIpList {
lbi = append(lbi, networkingv1beta1.IngressLoadBalancerIngress{IP: ep})
if net.ParseIP(ep) != nil {
lbi = append(lbi, networkingv1beta1.IngressLoadBalancerIngress{IP: ep})
} else {
lbi = append(lbi, networkingv1beta1.IngressLoadBalancerIngress{Hostname: ep})
}
}
sort.SliceStable(lbi, SortLbIngressListV1Beta1(lbi))
return lbi
}
func loadBalancerIngressAddress(ip, hostname string) string {
if ip != "" {
return ip
}
return hostname
}

View File

@@ -21,6 +21,8 @@ import (
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/config"
v1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
networkingv1beta1 "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/alibaba/higress/v2/pkg/ingress/kube/annotations"
@@ -206,6 +208,9 @@ func TestGenerateUniqueRouteName(t *testing.T) {
func TestGetLbStatusList(t *testing.T) {
clusterPrefix = "gw-123-"
svcName := clusterPrefix
aliyunHostname := "higress.cn-hangzhou.alb.aliyuncs.com"
awsHostname := "k8s-kubeingr-higressg-1234567890.eu-north-1.elb.amazonaws.com"
tencentHostname := "lb-12345678.clb.ap-guangzhou.tencentclb.com"
svcList := []*v1.Service{
{
ObjectMeta: metav1.ObjectMeta{
@@ -224,6 +229,57 @@ func TestGetLbStatusList(t *testing.T) {
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{
Hostname: awsHostname,
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{
Hostname: aliyunHostname,
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{
Hostname: tencentHostname,
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
@@ -295,17 +351,15 @@ func TestGetLbStatusList(t *testing.T) {
}
lbiList := GetLbStatusList(svcList)
if len(lbiList) != 4 {
t.Fatal("len should be 4")
}
if lbiList[0].IP != "1.1.1.1" {
t.Fatal("should be 1.1.1.1")
}
if lbiList[3].IP != "4.4.4.4" {
t.Fatal("should be 4.4.4.4")
}
assert.Equal(t, []v1.LoadBalancerIngress{
{IP: "1.1.1.1"},
{IP: "2.2.2.2"},
{IP: "3.3.3.3"},
{IP: "4.4.4.4"},
{Hostname: aliyunHostname},
{Hostname: awsHostname},
{Hostname: tencentHostname},
}, lbiList)
}
func TestSortRoutes(t *testing.T) {
@@ -1012,6 +1066,9 @@ func TestPartMd5(t *testing.T) {
func TestGetLbStatusListV1AndV1Beta1(t *testing.T) {
clusterPrefix = "gw-123-"
svcName := clusterPrefix
aliyunHostname := "higress.cn-hangzhou.alb.aliyuncs.com"
awsHostname := "k8s-kubeingr-higressg-1234567890.eu-north-1.elb.amazonaws.com"
tencentHostname := "lb-12345678.clb.ap-guangzhou.tencentclb.com"
svcList := []*v1.Service{
{
ObjectMeta: metav1.ObjectMeta{
@@ -1047,23 +1104,82 @@ func TestGetLbStatusListV1AndV1Beta1(t *testing.T) {
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{
Hostname: awsHostname,
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{
Hostname: aliyunHostname,
},
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: svcName,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
},
Status: v1.ServiceStatus{
LoadBalancer: v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{
Hostname: tencentHostname,
},
},
},
},
},
}
// Test the V1 version
t.Run("GetLbStatusListV1", func(t *testing.T) {
lbiList := GetLbStatusListV1(svcList)
assert.Equal(t, 2, len(lbiList), "There should be 2 entry points")
assert.Equal(t, "1.1.1.1", lbiList[0].IP, "The first IP should be 1.1.1.1")
assert.Equal(t, "2.2.2.2", lbiList[1].IP, "The second IP should be 2.2.2.2")
assert.Equal(t, []networkingv1.IngressLoadBalancerIngress{
{IP: "1.1.1.1"},
{IP: "2.2.2.2"},
{Hostname: aliyunHostname},
{Hostname: awsHostname},
{Hostname: tencentHostname},
}, lbiList)
})
// Test the V1Beta1 version
t.Run("GetLbStatusListV1Beta1", func(t *testing.T) {
lbiList := GetLbStatusListV1Beta1(svcList)
assert.Equal(t, 2, len(lbiList), "There should be 2 entry points")
assert.Equal(t, "1.1.1.1", lbiList[0].IP, "The first IP should be 1.1.1.1")
assert.Equal(t, "2.2.2.2", lbiList[1].IP, "The second IP should be 2.2.2.2")
assert.Equal(t, []networkingv1beta1.IngressLoadBalancerIngress{
{IP: "1.1.1.1"},
{IP: "2.2.2.2"},
{Hostname: aliyunHostname},
{Hostname: awsHostname},
{Hostname: tencentHostname},
}, lbiList)
})
}

View File

@@ -77,6 +77,7 @@ func NewController(client kube.Client, options common.Options, xdsUpdater model.
ClusterID: clusterId,
Revision: higressconfig.Revision,
}
istiogateway.SetGatewayClassName(options.GatewayClass)
istioController := istiogateway.NewController(client, client.CrdWatcher().WaitForCRD, opt, xdsUpdater)
if options.GatewaySelectorKey != "" {
istioController.DefaultGatewaySelector = map[string]string{options.GatewaySelectorKey: options.GatewaySelectorValue}

View File

@@ -27,7 +27,6 @@ import (
gw "sigs.k8s.io/gateway-api/apis/v1"
gatewayx "sigs.k8s.io/gateway-api/apisx/v1alpha1"
higressconstants "github.com/alibaba/higress/v2/pkg/config/constants"
networking "istio.io/api/networking/v1alpha3"
networkingclient "istio.io/client-go/pkg/apis/networking/v1"
kubesecrets "istio.io/istio/pilot/pkg/credentials/kube"
@@ -425,7 +424,7 @@ func BackendTLSPolicyCollection(
Kind: ptr.Of(gw.Kind(gvk.KubernetesGateway.Kind)),
Name: gw.ObjectName(g.Name),
}
ancestorStatus = append(ancestorStatus, setAncestorStatus(pr, status, i.Generation, conds, gw.GatewayController(higressconstants.ManagedGatewayController)))
ancestorStatus = append(ancestorStatus, setAncestorStatus(pr, status, i.Generation, conds, gw.GatewayController(managedGatewayController)))
}
status.Ancestors = mergeAncestors(status.Ancestors, ancestorStatus)
return status, res
@@ -635,14 +634,16 @@ func parentRefEqual(a, b gw.ParentReference) bool {
ptr.Equal(a.Port, b.Port)
}
var outControllers = sets.New(gw.GatewayController(higressconstants.ManagedGatewayController), constants.ManagedGatewayMeshController)
func isOutController(controller gw.GatewayController) bool {
return controller == managedGatewayController || controller == constants.ManagedGatewayMeshController
}
// mergeAncestors merges an existing ancestor with in incoming one. We preserve order, prune stale references set by our controller,
// and add any new references from our controller.
func mergeAncestors(existing []gw.PolicyAncestorStatus, incoming []gw.PolicyAncestorStatus) []gw.PolicyAncestorStatus {
n := 0
for _, x := range existing {
if !outControllers.Contains(x.ControllerName) {
if !isOutController(x.ControllerName) {
// Keep it as-is
existing[n] = x
n++

View File

@@ -21,7 +21,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8s "sigs.k8s.io/gateway-api/apis/v1"
higressconstants "github.com/alibaba/higress/v2/pkg/config/constants"
"istio.io/istio/pilot/pkg/features"
"istio.io/istio/pilot/pkg/model/kstatus"
"istio.io/istio/pkg/config/schema/gvk"
@@ -49,11 +48,12 @@ func createRouteStatus(
generation int64,
currentParents []k8s.RouteParentStatus,
) []k8s.RouteParentStatus {
controllerName := k8s.GatewayController(managedGatewayController)
parents := slices.Clone(currentParents)
parentIndexes := map[string]int{}
for idx, p := range parents {
// Only consider our own
if p.ControllerName != k8s.GatewayController(higressconstants.ManagedGatewayController) {
if p.ControllerName != controllerName {
continue
}
rs := parentRefString(p.ParentRef, objectNamespace)
@@ -186,14 +186,14 @@ func createRouteStatus(
var currentConditions []metav1.Condition
currentStatus := slices.FindFunc(currentParents, func(s k8s.RouteParentStatus) bool {
return parentRefString(s.ParentRef, objectNamespace) == myRef &&
s.ControllerName == k8s.GatewayController(higressconstants.ManagedGatewayController)
s.ControllerName == controllerName
})
if currentStatus != nil {
currentConditions = currentStatus.Conditions
}
ns := k8s.RouteParentStatus{
ParentRef: gw.OriginalReference,
ControllerName: k8s.GatewayController(higressconstants.ManagedGatewayController),
ControllerName: controllerName,
Conditions: setConditions(generation, currentConditions, conds),
}
// Parent ref already exists, insert in the same place

View File

@@ -27,6 +27,7 @@ import (
)
func TestCreateRouteStatus(t *testing.T) {
setGatewayClassNameForTest(t, "")
lastTransitionTime := metav1.Now()
parentRef := httpRouteSpec.ParentRefs[0]
parentStatus := []k8s.RouteParentStatus{
@@ -122,3 +123,29 @@ func TestCreateRouteStatus(t *testing.T) {
})
}
}
func TestCreateRouteStatusWithCustomController(t *testing.T) {
if runInGatewayClassSubprocess(t) {
return
}
setGatewayClassNameForTest(t, "higress-internal")
parentRef := httpRouteSpec.ParentRefs[0]
customController := k8s.GatewayController(managedGatewayController)
current := []k8s.RouteParentStatus{
{
ParentRef: parentRef,
ControllerName: k8s.GatewayController(higressconstants.ManagedGatewayController),
},
}
got := createRouteStatus([]RouteParentResult{{OriginalReference: parentRef}}, "default", 1, current)
if len(got) != 2 {
t.Fatalf("expected default and custom controller status entries, got %+v", got)
}
if got[0].ControllerName != k8s.GatewayController(higressconstants.ManagedGatewayController) {
t.Fatalf("expected existing default controller status to be preserved, got %+v", got)
}
if got[1].ControllerName != customController {
t.Fatalf("expected custom controller status %q, got %+v", customController, got)
}
}

View File

@@ -15,6 +15,10 @@
package istio
import (
"os"
"os/exec"
"regexp"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -77,6 +81,7 @@ var AlwaysReady = func(class schema.GroupVersionResource, stop <-chan struct{})
}
func setupController(t *testing.T, objs ...runtime.Object) *Controller {
setGatewayClassNameForTest(t, "")
kc := kube.NewFakeClient(objs...)
setupClientCRDs(t, kc)
stop := test.NewStop(t)
@@ -94,6 +99,57 @@ func setupController(t *testing.T, objs ...runtime.Object) *Controller {
return controller
}
func setupControllerWithGatewayClass(t *testing.T, gatewayClass string, objs ...runtime.Object) *Controller {
setGatewayClassNameForTest(t, gatewayClass)
kc := kube.NewFakeClient(objs...)
setupClientCRDs(t, kc)
stop := test.NewStop(t)
controller := NewController(
kc,
AlwaysReady,
controller.Options{KrtDebugger: krt.GlobalDebugHandler},
nil)
kc.RunAndWait(stop)
go controller.Run(stop)
cg := core.NewConfigGenTest(t, core.TestOptions{})
controller.Reconcile(cg.PushContext())
kube.WaitForCacheSync("test", stop, controller.HasSynced)
return controller
}
func setGatewayClassNameForTest(t *testing.T, gatewayClass string) {
t.Helper()
if gatewayClass != "" {
SetGatewayClassName(gatewayClass)
}
}
func runInGatewayClassSubprocess(t *testing.T) bool {
t.Helper()
const env = "HIGRESS_TEST_GATEWAY_CLASS_SUBPROCESS"
if os.Getenv(env) == t.Name() {
return false
}
cmd := exec.Command(os.Args[0], "-test.run=^"+regexp.QuoteMeta(t.Name())+"$", "-test.count=1")
cmd.Env = append(testEnvWithoutCoverage(), env+"="+t.Name())
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("gateway class subprocess failed: %v\n%s", err, out)
}
return true
}
func testEnvWithoutCoverage() []string {
var out []string
for _, kv := range os.Environ() {
if strings.HasPrefix(kv, "GOCOVERDIR=") {
continue
}
out = append(out, kv)
}
return out
}
func TestListInvalidGroupVersionKind(t *testing.T) {
controller := setupController(t)
@@ -135,3 +191,52 @@ func TestListGatewayResourceType(t *testing.T) {
assert.Equal(t, c.Spec, any(expectedgw))
}
}
func TestListGatewayResourceTypeWithCustomGatewayClass(t *testing.T) {
if runInGatewayClassSubprocess(t) {
return
}
customGatewayClass := "higress-internal"
customControllerName := higressconstant.ManagedGatewayController + "-" + customGatewayClass
defaultGateway := gatewaySpec.DeepCopy()
defaultGateway.GatewayClassName = k8s.ObjectName(higressconstant.DefaultGatewayClass)
customGateway := gatewaySpec.DeepCopy()
customGateway.GatewayClassName = k8s.ObjectName(customGatewayClass)
controller := setupControllerWithGatewayClass(t, customGatewayClass,
&k8sbeta.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: higressconstant.DefaultGatewayClass,
},
Spec: *gatewayClassSpec,
},
&k8sbeta.GatewayClass{
ObjectMeta: metav1.ObjectMeta{
Name: customGatewayClass,
},
Spec: k8s.GatewayClassSpec{
ControllerName: k8s.GatewayController(customControllerName),
},
},
&k8sbeta.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "default-gw",
Namespace: "ns1",
},
Spec: *defaultGateway,
},
&k8sbeta.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-gw",
Namespace: "ns1",
},
Spec: *customGateway,
})
dumpOnFailure(t, krt.GlobalDebugHandler)
cfg := controller.List(gvk.Gateway, "ns1")
assert.Equal(t, len(cfg), 1)
assert.Equal(t, cfg[0].Name, "custom-gw"+"-"+constants.KubernetesGatewayName+"-default")
assert.Equal(t, cfg[0].Namespace, "ns1")
assert.Equal(t, cfg[0].Spec, any(expectedgw))
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/alibaba/higress/v2/pkg/ingress/kube/util"
"istio.io/istio/pilot/pkg/credentials"
"net"
"net/netip"
"path"
inferencev1 "sigs.k8s.io/gateway-api-inference-extension/api/v1"
"sort"
@@ -1735,9 +1736,7 @@ func reportGatewayStatus(
setProgrammedCondition(gatewayConditions, internal, gatewayServices, warnings, allUsable)
addressesToReport := external
addrType := k8s.IPAddressType
if len(addressesToReport) == 0 {
addrType = k8s.HostnameAddressType
for _, hostport := range internal {
svchost, _, _ := net.SplitHostPort(hostport)
if !slices.Contains(pending, svchost) && !slices.Contains(addressesToReport, svchost) {
@@ -1745,12 +1744,21 @@ func reportGatewayStatus(
}
}
}
gs.Addresses = make([]k8s.GatewayStatusAddress, 0, len(addressesToReport))
for _, addr := range addressesToReport {
gs.Addresses = append(gs.Addresses, k8s.GatewayStatusAddress{
Value: addr,
Type: &addrType,
})
// Do not report an address until we are ready. But once we are ready, never remove the address.
if len(addressesToReport) > 0 {
gs.Addresses = make([]k8s.GatewayStatusAddress, 0, len(addressesToReport))
for _, addr := range addressesToReport {
var addrType k8s.AddressType
if _, err := netip.ParseAddr(addr); err == nil {
addrType = k8s.IPAddressType
} else {
addrType = k8s.HostnameAddressType
}
gs.Addresses = append(gs.Addresses, k8s.GatewayStatusAddress{
Value: addr,
Type: &addrType,
})
}
}
// Prune listeners that have been removed
haveListeners := getListenerNames(&obj.Spec)

View File

@@ -30,6 +30,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
k8s "sigs.k8s.io/gateway-api/apis/v1"
k8sbeta "sigs.k8s.io/gateway-api/apis/v1beta1"
"sigs.k8s.io/gateway-api/pkg/consts"
"sigs.k8s.io/yaml"
@@ -130,9 +131,9 @@ var services = []*model.Service{
Attributes: model.ServiceAttributes{
Namespace: "default",
Labels: map[string]string{
"higress.io/inferencepool-extension-service": "ext-proc-svc",
"higress.io/inferencepool-extension-port": "9002",
"higress.io/inferencepool-extension-failure-mode": "FailClose",
InferencePoolExtensionRefSvc: "ext-proc-svc",
InferencePoolExtensionRefPort: "9002",
InferencePoolExtensionRefFailureMode: "FailClose",
},
},
Ports: ports,
@@ -145,9 +146,9 @@ var services = []*model.Service{
Attributes: model.ServiceAttributes{
Namespace: "default",
Labels: map[string]string{
"higress.io/inferencepool-extension-service": "ext-proc-svc-2",
"higress.io/inferencepool-extension-port": "9002",
"higress.io/inferencepool-extension-failure-mode": "FailClose",
InferencePoolExtensionRefSvc: "ext-proc-svc-2",
InferencePoolExtensionRefPort: "9002",
InferencePoolExtensionRefFailureMode: "FailClose",
},
},
Ports: ports,
@@ -156,6 +157,36 @@ var services = []*model.Service{
return name
}())),
},
{
Attributes: model.ServiceAttributes{
Namespace: "default",
Labels: map[string]string{
InferencePoolExtensionRefSvc: "model1-epp",
InferencePoolExtensionRefPort: "9002",
InferencePoolExtensionRefFailureMode: "FailClose",
},
},
Ports: ports,
Hostname: host.Name(fmt.Sprintf("%s.default.svc.domain.suffix", func() string {
name, _ := InferencePoolServiceName("infpool-model1")
return name
}())),
},
{
Attributes: model.ServiceAttributes{
Namespace: "default",
Labels: map[string]string{
InferencePoolExtensionRefSvc: "model2-epp",
InferencePoolExtensionRefPort: "9002",
InferencePoolExtensionRefFailureMode: "FailClose",
},
},
Ports: ports,
Hostname: host.Name(fmt.Sprintf("%s.default.svc.domain.suffix", func() string {
name, _ := InferencePoolServiceName("infpool-model2")
return name
}())),
},
{
Attributes: model.ServiceAttributes{
@@ -570,9 +601,8 @@ func init() {
features.EnableAlphaGatewayAPI = true
features.EnableAmbientWaypoints = true
features.EnableAmbientMultiNetwork = true
// Recompute with ambient enabled
classInfos = getClassInfos()
builtinClasses = getBuiltinClasses()
// Recompute with the desired feature flags.
SetGatewayClassName("")
}
type TestStatusQueue struct {
@@ -794,6 +824,128 @@ func TestConvertResources(t *testing.T) {
}
}
func TestReportGatewayStatusAddressType(t *testing.T) {
cases := []struct {
name string
ingresses []corev1.LoadBalancerIngress
want map[string]k8s.AddressType
}{
{
name: "load balancer IPv4",
ingresses: []corev1.LoadBalancerIngress{
{IP: "47.98.1.2"},
},
want: map[string]k8s.AddressType{
"47.98.1.2": k8s.IPAddressType,
},
},
{
name: "load balancer IPv6",
ingresses: []corev1.LoadBalancerIngress{
{IP: "2001:db8::1"},
},
want: map[string]k8s.AddressType{
"2001:db8::1": k8s.IPAddressType,
},
},
{
name: "load balancer hostname",
ingresses: []corev1.LoadBalancerIngress{
{Hostname: "k8s-higress-higressg-1234567890.us-west-2.elb.amazonaws.com"},
},
want: map[string]k8s.AddressType{
"k8s-higress-higressg-1234567890.us-west-2.elb.amazonaws.com": k8s.HostnameAddressType,
},
},
{
name: "mixed load balancer addresses",
ingresses: []corev1.LoadBalancerIngress{
{IP: "47.98.1.2"},
{Hostname: "higress.cn-hangzhou.alb.aliyuncs.com"},
},
want: map[string]k8s.AddressType{
"47.98.1.2": k8s.IPAddressType,
"higress.cn-hangzhou.alb.aliyuncs.com": k8s.HostnameAddressType,
},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "higress-gateway",
Namespace: "higress-system",
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
Protocol: corev1.ProtocolTCP,
},
},
},
Status: corev1.ServiceStatus{
LoadBalancer: corev1.LoadBalancerStatus{
Ingress: tt.ingresses,
},
},
}
stop := test.NewStop(t)
kc := kube.NewFakeClient(svc)
kc.RunAndWait(stop)
ctx := NewGatewayContext(nil, constants.DefaultClusterName, kc, "cluster.local")
gw := &k8sbeta.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "higress-gateway",
Namespace: "higress-system",
Generation: 1,
},
Spec: k8sbeta.GatewaySpec{
GatewayClassName: "higress",
Listeners: []k8sbeta.Listener{
{
Name: "http",
Port: 80,
Protocol: k8s.HTTPProtocolType,
},
},
},
}
gs := &k8sbeta.GatewayStatus{}
servers := []*istio.Server{
{
Port: &istio.Port{
Name: "http",
Number: 80,
Protocol: "HTTP",
},
},
}
reportGatewayStatus(&ctx, gw, gs, []string{"higress-gateway.higress-system.svc.cluster.local"}, servers, 0, nil)
if len(gs.Addresses) != len(tt.want) {
t.Fatalf("expected %d addresses, got %d: %#v", len(tt.want), len(gs.Addresses), gs.Addresses)
}
for _, got := range gs.Addresses {
wantType, ok := tt.want[got.Value]
if !ok {
t.Fatalf("unexpected address value %q in %#v", got.Value, gs.Addresses)
}
if got.Type == nil {
t.Fatalf("expected address %q type %q, got nil", got.Value, wantType)
}
if *got.Type != wantType {
t.Fatalf("expected address %q type %q, got %q", got.Value, wantType, *got.Type)
}
}
})
}
}
func setupClientCRDs(t *testing.T, kc kube.CLIClient) {
for _, crd := range []schema.GroupVersionResource{
gvr.KubernetesGateway,

View File

@@ -48,15 +48,33 @@ type classInfo struct {
addressType gateway.AddressType
}
var classInfos = getClassInfos()
var (
gatewayClassName = gateway.ObjectName(higressconstants.DefaultGatewayClass)
managedGatewayController = gateway.GatewayController(higressconstants.ManagedGatewayController)
classInfos = getClassInfos()
builtinClasses = getBuiltinClasses()
)
var builtinClasses = getBuiltinClasses()
// SetGatewayClassName configures the single GatewayClassName this process owns.
func SetGatewayClassName(gatewayClass string) {
if gatewayClass == "" {
gatewayClass = higressconstants.DefaultGatewayClass
}
gatewayClassName = gateway.ObjectName(gatewayClass)
if gatewayClass == higressconstants.DefaultGatewayClass {
managedGatewayController = gateway.GatewayController(higressconstants.ManagedGatewayController)
} else {
managedGatewayController = gateway.GatewayController(higressconstants.ManagedGatewayController + "-" + gatewayClass)
}
classInfos = getClassInfos()
builtinClasses = getBuiltinClasses()
}
func getBuiltinClasses() map[gateway.ObjectName]gateway.GatewayController {
res := map[gateway.ObjectName]gateway.GatewayController{
// Start - Updated by Higress
//gateway.ObjectName(features.GatewayAPIDefaultGatewayClass): gateway.GatewayController(features.ManagedGatewayController),
higressconstants.DefaultGatewayClass: higressconstants.ManagedGatewayController,
gatewayClassName: managedGatewayController,
// End - Updated by Higress
}
// Start - Commented by Higress
@@ -80,8 +98,8 @@ func getBuiltinClasses() map[gateway.ObjectName]gateway.GatewayController {
func getClassInfos() map[gateway.GatewayController]classInfo {
// Start - Updated by Higress
m := map[gateway.GatewayController]classInfo{
gateway.GatewayController(higressconstants.ManagedGatewayController): {
controller: higressconstants.ManagedGatewayController,
managedGatewayController: {
controller: string(managedGatewayController),
description: "The default Higress GatewayClass",
templates: "kube-gateway",
defaultServiceType: corev1.ServiceTypeLoadBalancer,

View File

@@ -38,8 +38,11 @@ func GatewayClassesCollection(
krt.Collection[GatewayClass],
) {
return krt.NewStatusCollection(gatewayClasses, func(ctx krt.HandlerContext, obj *gateway.GatewayClass) (*gateway.GatewayClassStatus, *GatewayClass) {
_, known := classInfos[obj.Spec.ControllerName]
if !known {
if gatewayv1.ObjectName(obj.Name) != gatewayv1.ObjectName(gatewayClassName) ||
obj.Spec.ControllerName != managedGatewayController {
return nil, nil
}
if _, known := classInfos[obj.Spec.ControllerName]; !known {
return nil, nil
}
status := obj.Status.DeepCopy()

View File

@@ -16,13 +16,13 @@ package istio
import (
"fmt"
"github.com/alibaba/higress/v2/pkg/config/constants"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gateway "sigs.k8s.io/gateway-api/apis/v1beta1"
"github.com/alibaba/higress/v2/pkg/config/constants"
"istio.io/istio/pkg/kube"
"istio.io/istio/pkg/kube/kclient/clienttest"
"istio.io/istio/pkg/test"
@@ -30,6 +30,7 @@ import (
)
func TestClassController(t *testing.T) {
setGatewayClassNameForTest(t, "")
client := kube.NewFakeClient()
cc := NewClassController(client)
classes := clienttest.Wrap(t, cc.classes)
@@ -91,3 +92,70 @@ func TestClassController(t *testing.T) {
deleteClass("something-else")
expectClass("something-else", "")
}
func TestClassControllerWithCustomGatewayClass(t *testing.T) {
if runInGatewayClassSubprocess(t) {
return
}
gatewayClass := "higress-internal"
setGatewayClassNameForTest(t, gatewayClass)
client := kube.NewFakeClient()
controllerName := string(gateway.GatewayController(constants.ManagedGatewayController + "-" + gatewayClass))
cc := NewClassController(client)
classes := clienttest.Wrap(t, cc.classes)
stop := test.NewStop(t)
client.RunAndWait(stop)
go cc.Run(stop)
expectClass := func(name, controller string) {
t.Helper()
retry.UntilSuccessOrFail(t, func() error {
gc := classes.Get(name, "")
if controller == "" {
if gc == nil {
return nil
}
return fmt.Errorf("expected no class, got %v", gc.Spec.ControllerName)
}
if gc == nil {
return fmt.Errorf("expected class %v, got none", controller)
}
if gateway.GatewayController(controller) != gc.Spec.ControllerName {
return fmt.Errorf("expected class %v, got %v", controller, gc.Spec.ControllerName)
}
return nil
}, retry.Timeout(time.Second*3))
}
expectClass(gatewayClass, controllerName)
expectClass(constants.DefaultGatewayClass, "")
}
func TestSetGatewayClassName(t *testing.T) {
if runInGatewayClassSubprocess(t) {
return
}
SetGatewayClassName("")
if gatewayClassName != gateway.ObjectName(constants.DefaultGatewayClass) {
t.Fatalf("expected default gateway class %q, got %q", constants.DefaultGatewayClass, gatewayClassName)
}
if managedGatewayController != gateway.GatewayController(constants.ManagedGatewayController) {
t.Fatalf("expected default controller %q, got %q", constants.ManagedGatewayController, managedGatewayController)
}
customClass := "higress-internal"
SetGatewayClassName(customClass)
customController := gateway.GatewayController(constants.ManagedGatewayController + "-" + customClass)
if gatewayClassName != gateway.ObjectName(customClass) {
t.Fatalf("expected custom gateway class %q, got %q", customClass, gatewayClassName)
}
if managedGatewayController != customController {
t.Fatalf("expected custom controller %q, got %q", customController, managedGatewayController)
}
if got := builtinClasses[gateway.ObjectName(customClass)]; got != customController {
t.Fatalf("expected builtin class controller %q, got %q", customController, got)
}
if _, exists := builtinClasses[gateway.ObjectName(constants.DefaultGatewayClass)]; exists {
t.Fatalf("custom config should not include default gateway class %q", constants.DefaultGatewayClass)
}
}

View File

@@ -52,8 +52,6 @@ const (
// ControllerName is the name of this controller for labeling resources it manages
const ControllerName = "inference-controller"
var supportedControllers = getSupportedControllers()
func getSupportedControllers() sets.Set[gatewayv1.GatewayController] {
ret := sets.New[gatewayv1.GatewayController]()
for _, controller := range builtinClasses {
@@ -241,7 +239,7 @@ func findGatewayParents(
for _, parentStatus := range route.Status.Parents {
// Only consider parents managed by our supported controllers (from supportedControllers variable)
// This filters out parents from other controllers we don't manage
if !supportedControllers.Contains(parentStatus.ControllerName) {
if !getSupportedControllers().Contains(parentStatus.ControllerName) {
continue
}
@@ -354,7 +352,7 @@ func calculateAcceptedStatus(
// Check if this route has our gateway as a parent and if it's accepted
for _, parentStatus := range route.Status.Parents {
// Only consider parents managed by supported controllers
if !supportedControllers.Contains(parentStatus.ControllerName) {
if !getSupportedControllers().Contains(parentStatus.ControllerName) {
continue
}

Some files were not shown because too many files have changed in this diff Show More