Compare commits

...

183 Commits

Author SHA1 Message Date
johnlanni
8e426a5e99 feat: add higress-deep-troubleshooting skill
Distill the investigation methodology used in #4034 (Wasm CPU-spin root
cause across envoy + proxy-wasm-cpp-host forks) into a reusable skill
that future investigations can pick up and extend.

Structure:
- SKILL.md: entry point with trigger description and code map
- references/: investigation methodology + code-trace verification rules
- patterns/: accumulating case-study library; first entry is the #4034
  drain-to-local fix (covers both CPU-spin and #326 stage-mismatch)
- templates/: investigation report + state-trace table formats

The patterns/ directory is the living part — each major investigation
adds a pattern capturing the technical root cause plus meta-lessons
(what tripped up the first analysis round, what the maintainer caught,
what generalizes).

Origin: #4034 / #4064 issue-spec investigation session
s-6de6de055cd791bc3f3b.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-07 12:59:07 +00: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 66599 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,61 @@
---
name: higress-deep-troubleshooting
description: Deep-dive troubleshooting methodology for Higress gateway bugs that require code-trace evidence across the higress-group/envoy and higress-group/proxy-wasm-cpp-host forks — CPU spins, infinite loops, Wasm VM reentrancy, filter-chain stage-mismatch, callback-ordering issues, SaveRestoreContext/current_context_ bugs. Use when investigating a complex Higress/Wasm/proxy-wasm incident that needs root-cause analysis with file:line citations, when ramping up on a reported CPU-spin or crash, or when verifying whether a proposed fix covers related bug classes. Accumulates case-study patterns under patterns/ — add a new pattern after each major investigation.
---
# Higress Deep Troubleshooting
A methodology plus an accumulating pattern library for deep-diving into Higress gateway bugs that require code-level root cause analysis. The codebase spans two Higress-maintained forks — [`higress-group/envoy`](https://github.com/higress-group/envoy) (filter chain, Wasm `Context` integration) and [`higress-group/proxy-wasm-cpp-host`](https://github.com/higress-group/proxy-wasm-cpp-host) (Wasm VM host runtime, `WasmBase`, `ContextBase`) — and bugs often cross the boundary between them. This skill exists so that the next investigation doesn't start from scratch.
## When to use
Reach for this skill when ANY of these apply:
- A Higress issue reports a CPU spin, infinite loop, crash, or stage-mismatched behavior.
- The bug involves the Wasm plugin lifecycle, `addAfterVmCallAction` / `doAfterVmCallActions`, `SaveRestoreContext`, `current_context_`, or the filter chain reentry paths.
- You need to verify whether a one-repo fix also covers an upstream bug class (e.g., `proxy-wasm/proxy-wasm-cpp-host#326`).
- You're ramping up on an incident and need a structured way to collect evidence before proposing a fix.
- The maintainer is pushing back on a conclusion and you need to re-verify with code.
Do NOT reach for this skill for routine plugin development (`higress-wasm-go-plugin` covers that), config issues, or anything solvable by reading a single file without cross-repo context.
## Investigation methodology (brief)
1. **Symptom → code surface.** From perf data / repro steps / stack trace, identify which functions and files are involved. Note which fork each file lives in.
2. **Parallel investigation.** Dispatch focused worker agents for independent aspects (envoy side, proxy-wasm-cpp-host side, upstream references). Each worker returns file:line evidence — never narration.
3. **Code-trace verification.** Every claim must cite `file:line`. No "I think it does X" — only "see envoy context.cc:2085 where it calls ... synchronously".
4. **Synthesize root cause.** Combine worker findings into a numbered call-chain trace, showing program state (queue contents, `current_context_` value, etc.) at each hop.
5. **Fix design + side effects.** Propose a primitive, analyze what else it touches, check if it covers related bug classes upstream.
6. **Verify when challenged.** If the maintainer pushes back, re-verify with code — don't restate the conclusion. The earlier analysis is often wrong in some detail that only a fresh code read catches.
Full process: [`references/investigation-methodology.md`](references/investigation-methodology.md). Verification patterns: [`references/code-trace-verification.md`](references/code-trace-verification.md).
## Accumulated patterns
Each major investigation that uses this skill should add a pattern under `patterns/`. Patterns are the "living" part of this skill — they capture both the technical root cause AND the meta-lessons (what tripped up the analysis, what the maintainer caught, what the fix primitive generalizes to).
Current patterns:
- [`patterns/2026-07-wasm-doaftervmcallactions-reentry.md`](patterns/2026-07-wasm-doaftervmcallactions-reentry.md) — #4034 CPU 100% from `onRedisCallFailure` + `injectEncodedDataToFilterChain` nested reentry. Root cause: unguarded `while(!empty())` drain in `WasmBase::doAfterVmCallActions`. Fix: drain-to-local via `std::deque::swap`. Meta-lesson: the same primitive covers the upstream #326 stage-mismatch class, which the first analysis round missed.
To add a new pattern after an investigation, see [`patterns/README.md`](patterns/README.md).
## Templates
- [`templates/investigation-report.md`](templates/investigation-report.md) — structure for a new investigation's findings.
- [`templates/code-trace-table.md`](templates/code-trace-table.md) — step-by-step state-trace table format (very effective for communicating queue/state transitions to maintainers).
## Key code maps (quick orientation)
When investigating a Wasm-related Higress bug, the usual suspects:
| Concern | Repo | File |
| --- | --- | --- |
| Plugin callback entry points (`onRequestHeaders`, `onResponseBody`, `onHttpCallResponse`, etc.) | `higress-group/proxy-wasm-cpp-host` | `src/context.cc` |
| Deferred-action queue (`addAfterVmCallAction`, `doAfterVmCallActions`, `DeferAfterCallActions`) | `higress-group/proxy-wasm-cpp-host` | `include/proxy-wasm/wasm.h`, `src/context.cc` |
| `SaveRestoreContext` / `current_context_` | `higress-group/proxy-wasm-cpp-host` | `include/proxy-wasm/wasm_vm.h`, `src/exports.cc` |
| Higress envoy `Context` (filter chain integration, foreign functions) | `higress-group/envoy` | `source/extensions/common/wasm/context.cc` |
| Foreign-function registration (`proxy_inject_encoded_data_to_filter_chain`, etc.) | `higress-group/envoy` | `source/extensions/common/wasm/foreign.cc` |
| envoy fork's pinned proxy-wasm-cpp-host commit | `higress-group/envoy` | `bazel/repository_locations.bzl` |
The Higress envoy fork pins a specific `proxy-wasm-cpp-host` commit via bazel — always check which fork commit is pinned before reasoning about `WasmBase` behavior, because the fork diverges from upstream (RedisCall, rebuild/recover, weak_handle additions).

View File

@@ -0,0 +1,116 @@
# Wasm `doAfterVmCallActions` reentry — CPU-spin and stage-mismatch, fixed by drain-to-local
> Source: [#4034](https://github.com/higress-group/higress/issues/4034) — Wasm 插件死循环onRedisCallFailure + injectEncodedDataToFilterChain 嵌套导致 Envoy CPU 100% 跑满
> Upstream relative: [proxy-wasm/proxy-wasm-cpp-host#326](https://github.com/proxy-wasm/proxy-wasm-cpp-host/issues/326)
> Date: 2026-07
> Status: fix designed (SPEC-001), implementation pending
## Symptom
`release-2.2.2` Envoy pods CPU 100% on all 6 pods within ~3 minutes under concurrent load. Trigger: Redis unreachable + plugin calls `InjectEncodedDataToFilterChain` from an HTTP/Redis callback. perf:
| Overhead | Function |
| --- | --- |
| 39.21% | `Envoy::Extensions::Common::Wasm::Context::onRedisCallFailure` |
| 25.41% | `proxy_wasm::WasmBase::doAfterVmCallActions` |
| **合计** | **64.62%** |
## Root cause
`WasmBase::doAfterVmCallActions` (`higress-group/proxy-wasm-cpp-host` `include/proxy-wasm/wasm.h:158-169`) drains `after_vm_call_actions_` in an unguarded `while(!empty())` loop:
```cpp
while (!self->after_vm_call_actions_.empty()) {
auto f = std::move(self->after_vm_call_actions_.front());
self->after_vm_call_actions_.pop_front();
f(); // if f() re-adds an action, loop never terminates
}
```
Several Envoy-side callbacks re-defer themselves when `current_context_ != nullptr` (meaning: "VM is still on the stack, defer until it unwinds"):
- `Context::onRedisCallFailure` (envoy `context.cc:1273-1277`)
- `Context::onRedisCallSuccess` (envoy `context.cc:1243-1249`)
- `Context::onHttpCallSuccess` (envoy `context.cc:2352-2358`)
- `Context::onHttpCallFailure` (envoy `context.cc:2382-2386`)
Under a synchronous reentry path — `injectEncodedDataToFilterChain` (envoy `context.cc:2085-2091`) is called from inside an HTTP/Redis callback — the outer `SaveRestoreContext` keeps `current_context_` non-null even after the inner `~SaveRestoreContext` unwinds. So the drain loop pops a self-reenqueueing callback, fires it, it sees `current_context_ != nullptr`, re-adds itself, loop iterates again → infinite → CPU 100%.
Full call chain (10 hops, each with file:line) is in [the #4034 proposal issue](https://github.com/higress-group/higress/issues/4064) body and SPEC-001.
## Fix
Swap the member queue to a local deque before iterating, so any action re-added during the drain lands in the (now-empty) member queue and is picked up by the next-outer `DeferAfterCallActions` frame — not re-fired within the same drain.
```cpp
void doAfterVmCallActions() {
if (!after_vm_call_actions_.empty()) {
auto self = shared_from_this();
std::deque<std::function<void()>> local;
local.swap(self->after_vm_call_actions_);
for (auto& f : local) f();
}
}
```
`std::deque::swap` is O(1) whole-content exchange. After swap: `local` has the pre-existing queue, `self->after_vm_call_actions_` is empty. The for-loop iterates `local`; any `addAfterVmCallAction` call during iteration appends to the empty member, invisible to this drain.
## Coverage
The same primitive covers BOTH:
- **#4034 (CPU-spin)**: re-added actions go to the empty member queue → picked up by next outer frame → loop terminates.
- **#326 (stage-mismatch)**: pre-existing actions stay in `local` → inner drain (from a nested `~DeferAfterCallActions`) sees empty member → does nothing → pre-existing actions fire in the outer drain's correct phase.
The coverage of #326 is non-obvious — see Meta-lessons below.
## Evidence
| Claim | Citation |
| --- | --- |
| Unguarded drain loop | `higress-group/proxy-wasm-cpp-host` `include/proxy-wasm/wasm.h:158-169` |
| Self-reenqueue guard `if (current_context_ != nullptr)` | `higress-group/envoy` `source/extensions/common/wasm/context.cc:1273-1277` (onRedisCallFailure); also lines 1243, 2352, 2382 |
| Sync `injectEncodedDataToFilterChain` (the trigger) | `higress-group/envoy` `source/extensions/common/wasm/context.cc:2085-2091` |
| Async sibling `injectEncodedDataToFilterChainOnHeader` (the contrast) | `higress-group/envoy` `source/extensions/common/wasm/context.cc:2093-2103` |
| `sendLocalReply` is deferred via `addAfterVmCallAction` | `higress-group/envoy` `source/extensions/common/wasm/context.cc:1638-1657` |
| `continueDecoding` is deferred via `addAfterVmCallAction` | `higress-group/envoy` `source/extensions/common/wasm/context.cc:1508-1514` |
| `~DeferAfterCallActions` drains unconditionally | `higress-group/proxy-wasm-cpp-host` `src/context.cc` (top, `~DeferAfterCallActions` calls `wasm_->doAfterVmCallActions()`) |
| `SaveRestoreContext` save/restore semantics | `higress-group/proxy-wasm-cpp-host` `include/proxy-wasm/wasm_vm.h:392-405` |
| `current_context_` is thread_local | `higress-group/proxy-wasm-cpp-host` `src/exports.cc:26` |
## Meta-lessons
This investigation had three rounds of analysis. The meta-lessons are the most valuable part for future investigators.
### Lesson 1: a single primitive can cover two bug classes — check coverage explicitly
The first analysis round concluded "Layer A fixes #4034 but NOT #326" and proposed them as separate work items. That was wrong. The `swap-to-local` primitive covers BOTH because:
- For #4034: re-added actions go to the empty member.
- For #326: pre-existing actions stay in `local`, invisible to the nested drain.
**Generalization**: whenever a fix changes how a queue/collection is iterated (swap-to-local, snapshot-then-iterate, copy-on-iterate), check whether it ALSO changes cross-frame or cross-stage visibility. These primitives often cover more than the primary bug.
### Lesson 2: model the PATCHED code, not the unpatched code
When analyzing whether a fix covers a related bug, it's easy to accidentally reason about the current (unpatched) behavior instead of the patched behavior. The first analysis round said "the inner drain sees `[continueDecoding]`" — which is true for the CURRENT code but FALSE for the patched code, because the patch's swap empties the member before the inner drain runs.
**Generalization**: when someone proposes a fix and asks "does this also cover X?", re-read the PATCHED code, not just the diff. The patch changes program state at moments you might not be tracking.
### Lesson 3: "X is deferred" vs "X is direct" is a load-bearing distinction
Several rounds of analysis hinged on whether `sendLocalReply` and `continueDecoding` are called directly or deferred via `addAfterVmCallAction`. Both are deferred — and that determines the queue order that produces the bug.
**Generalization**: when a function's name suggests "does X immediately" (like `sendLocalReply`), don't trust the name. Read the function body and check whether it actually calls X directly or wraps it in `addAfterVmCallAction` / `dispatcher().post(...)` / similar defer primitives. The defer-vs-direct distinction changes everything about reentrancy analysis.
### Lesson 4: maintainer pushback is a signal to re-verify, not restate
When the maintainer challenged the "Layer A doesn't fix #326" conclusion, the right move was to re-read the code with their specific objection in mind — not to restate the conclusion with different words. The re-read found that the swap-to-local empties the member queue, which the first analysis had missed.
**Generalization**: maintainer pushback is high-signal information. They know the codebase. If they say "I think X", dispatch a fresh worker to verify X with code evidence. Don't confirm your own previous answer; that's confirmation bias.
### Lesson 5: the #326 reproducer's plugin call order matters
The #326 reproducer calls `SendHttpResponse` THEN `ResumeHttpRequest`. If the order were reversed, the queue would be `[continueDecoding, sendLocalReply]`, FIFO drain would pop `continueDecoding` first (in the correct phase), then `sendLocalReply` (triggering encoder reentry with empty queue) — and there would be no stage mismatch even WITHOUT Layer A.
**Generalization**: when analyzing a reproducer, note the exact order of plugin calls. Stage-mismatch bugs often depend on call order, and a "harmless" reorder can mask the bug.

View File

@@ -0,0 +1,71 @@
# Patterns — How to Add a New Investigation Case-Study
This directory accumulates case-studies from past deep investigations. Each pattern captures both the technical root cause AND the meta-lessons (what tripped up the analysis, what the maintainer caught, what the fix primitive generalizes to). The library grows by one entry per major investigation.
## When to add a pattern
Add a pattern after an investigation that:
- Required cross-repo code-trace evidence (envoy + proxy-wasm-cpp-host, or similar).
- Resulted in a non-trivial fix (not just a one-line config change).
- Has a meta-lesson worth carrying forward (a gotcha, a verification failure, a primitive that generalizes).
If the investigation was routine (single-file bug, obvious fix, no pushback), skip the pattern — the diff IS the documentation.
## File naming
`YYYY-MM-short-slug.md` where the date is when the investigation concluded. Examples:
- `2026-07-wasm-doaftervmcallactions-reentry.md`
- `2026-09-filter-chain-headers-double-dispatch.md`
- `2026-11-redis-pool-exhaustion-under-failover.md`
## Pattern structure
Use this template. Sections in `[brackets]` are required; sections in `<angle brackets>` are optional but encouraged.
```markdown
# <Bug class one-line summary>
> Source: #ISSUE_NUMBER — <issue title>
> Date: YYYY-MM
> Status: <fixed / investigating / deferred>
## Symptom
<What observable behavior was wrong? Include perf data / repro steps if relevant.>
## Root cause
<One-paragraph explanation. Cite file:line for key claims.>
<Numbered call-chain trace, one hop per line, with file:line.>
## Fix
<The primitive. Code snippet if non-trivial.>
<Why it fixes the bug — the causal chain.>
## Coverage
<What bug classes does this fix cover? What does it NOT cover? Why?>
## Evidence
<File:line citations grouped by claim.>
## Meta-lessons
<What tripped up the first analysis round? What did the maintainer catch? What generalizes to other bugs in this area?>
<If the analysis was wrong initially and then corrected, document BOTH the wrong reasoning AND the correction — this is the most valuable part for future investigators.>
```
## Worked example
See [`2026-07-wasm-doaftervmcallactions-reentry.md`](2026-07-wasm-doaftervmcallactions-reentry.md) for a complete pattern that follows this structure, including a meta-lesson section documenting an initial wrong conclusion that was corrected after maintainer pushback.
## Linking from new investigations
When a new investigation reuses a primitive or hits a similar gotcha to a past pattern, link to the pattern in the new investigation's report. Patterns are most valuable when they're cross-referenced — the second time you see a `std::deque::swap`-style fix, you should be able to find the #4034 pattern and reason by analogy.

View File

@@ -0,0 +1,73 @@
# Code-Trace Verification
The format and rules for verifying claims with code evidence during a deep investigation. Maintainers trust analysis they can audit; file:line citations are the audit trail.
## Core rule
**Every claim must cite `file:line`.** If you can't cite it, you haven't read it; if you haven't read it, you don't know.
## Acceptable evidence
| Claim type | What to cite |
| --- | --- |
| "Function X does Y" | `repo/path/file.cc:NN` + ≤5-line excerpt |
| "Function X is synchronous" | The call site that lacks `dispatcher().post(...)` (or equivalent) + the sibling function that DOES use it, as contrast |
| "The queue state is `[a, b]` at this point" | The sequence of `push_back` / `pop_front` calls leading to that state, each with file:line |
| "The maintainer's claim is wrong" | Re-state their claim, then cite the specific code that contradicts it |
| "This fix covers bug class Y too" | The mechanism by which it covers Y, with the call chain showing why |
## Unacceptable evidence
- "I think" / "probably" / "should be" — read the code.
- "Similar to upstream" — check the fork commit pin in `bazel/repository_locations.bzl`.
- "The function name implies X" — names lie. `injectEncodedDataToFilterChain` sounds innocuous; it's the trigger for a CPU-spin bug.
- Reasoning from the bug report without code verification — the report describes symptoms.
## State-trace table format
The most effective format for communicating queue/state transitions. Use it whenever the bug involves a sequence of state changes (queue operations, `current_context_` saves/restores, reference-counting transitions).
Template:
| Step | Action | `<state variable 1>` | `<state variable 2>` |
| --- | --- | --- | --- |
| 1 | Enter context | `[]` | `nullptr` |
| 2 | Operation X | `[x]` | `nullptr` |
| 3 | Operation Y | `[x, y]` | `ctx` |
| ... | ... | ... | ... |
Rules:
- One row per discrete state change.
- State columns should be the variables that matter for the bug (queue contents, `current_context_`, depth counter, etc.).
- Mark the critical transition with ★ and explain it below the table.
- If a state is "unknown" or "depends on plugin behavior", say so — don't fake precision.
## Worked example (from #4034)
The #4034 investigation hinged on the queue state at the moment of a nested drain. The state-trace table that settled the disagreement:
| Step | Action | `self->after_vm_call_actions_` | outer drain's `local` |
| --- | --- | --- | --- |
| 1 | Enter `onHttpCallResponse` | `[]` | — |
| 2 | Plugin calls `SendHttpResponse` | `[sendLocalReply]` | — |
| 3 | Plugin calls `ResumeHttpRequest` | `[sendLocalReply, continueDecoding]` | — |
| 4 | Outer `~DeferAfterCallActions` fires | same | — |
| 5 | **Layer A: `local.swap(after_vm_call_actions_)`** ★ | `[]` | `[sendLocalReply, continueDecoding]` |
| 6 | iter 1: fire `sendLocalReply``encodeHeaders``onResponseHeaders` | `[]` | same |
| 7 | Inner `~DeferAfterCallActions` fires | `[]` (empty → no-op) | same |
| 8 | iter 2: fire `continueDecoding` in outer context | `[]` | same |
The ★ at step 5 is the key insight: `std::deque::swap` empties the member, so the inner drain at step 7 sees an empty queue and does nothing. The earlier (wrong) analysis confused "queue before swap" with "queue at inner-drain time".
## Re-verification protocol (when the maintainer pushes back)
When a maintainer disagrees with your conclusion:
1. **Don't restate the conclusion.** Restating without new evidence wastes their time.
2. **Identify the specific point of disagreement.** What exactly do they claim?
3. **Dispatch a fresh worker** with the specific objection. The worker brief should say: "Maintainer claims X. Verify whether X is true. Cite file:line."
4. **If the worker confirms the maintainer**: acknowledge the error publicly, explain what you missed, update artifacts.
5. **If the worker refutes the maintainer**: respond with the specific code evidence, walk through the state-trace table, be respectful.
The third verification (yours) doesn't count if it just restates the second (also yours). You need to actually re-read the code with the maintainer's objection in mind, not just re-derive your previous answer.

View File

@@ -0,0 +1,118 @@
# Investigation Methodology
The step-by-step process for deep-diving a Higress bug. This is the canonical workflow; adapt it to the bug at hand but don't skip the evidence-collection steps.
## 0. Before you start: scope the investigation
Confirm the bug is real and worth a deep dive. Signs it's worth it:
- Reproducer exists (even if intermittent).
- perf data or stack trace points at specific functions.
- The fix likely requires code changes (not config/docs).
- The blast radius is significant (production-affecting, multiple users).
If the bug is a one-off without a reproducer, or clearly a user-config issue, don't burn a deep investigation on it.
## 1. Symptom → code surface
Read the bug report and extract:
- **Symptom**: what observable behavior is wrong? (CPU spin, crash, wrong response, hang.)
- **Trigger conditions**: what's the minimal repro path?
- **perf / stack data**: which functions dominate?
Then map symptoms to code surfaces. For Wasm-related bugs, the surface is almost always split across:
- `higress-group/envoy` `source/extensions/common/wasm/context.cc` — Higress-specific `Context` subclass, foreign functions like `injectEncodedDataToFilterChain`.
- `higress-group/proxy-wasm-cpp-host` `include/proxy-wasm/wasm.h` + `src/context.cc` — VM host runtime, `WasmBase`, `ContextBase`, deferred-action queue.
**Always check the envoy fork's `bazel/repository_locations.bzl` for the pinned `proxy-wasm-cpp-host` commit.** The fork diverges from upstream; reasoning about upstream code without checking the pin leads to wrong conclusions.
## 2. Parallel investigation (worker dispatch)
For non-trivial bugs, dispatch focused worker agents IN PARALLEL for independent aspects. Each worker gets a self-contained brief (it has no shared context with you). Example dispatch for a Wasm reentrancy bug:
- **Worker A (envoy side)**: verify the trigger path in `higress-group/envoy`. Find the synchronous call, the self-reenqueueing callbacks, the foreign-function registration. Return file:line evidence.
- **Worker B (proxy-wasm-cpp-host side)**: verify the engine. Find the queue, the drain loop, the `SaveRestoreContext` semantics. Return file:line evidence.
- **Worker C (upstream references)**: fetch any linked upstream issues/PRs, determine if they're related/resolved/portable.
Worker rules:
- Each worker is **research-only** unless you explicitly authorize code changes. Don't let workers edit code on their own initiative.
- Each worker must **cite file:line for every claim**. Reject worker reports that say "the function does X" without a citation.
- Cap report length (e.g., 600-800 words) to keep synthesis tractable.
- Use `general-purpose` agent type for investigations that need to clone repos and reason across files; use `Explore` for narrow single-repo lookups.
## 3. Code-trace verification
Every claim in the final analysis must reduce to a file:line citation. The output format that works best for maintainer communication is the **step-by-step state-trace table**:
| Step | Action | Member queue state | Local queue state |
| --- | --- | --- | --- |
| 1 | Enter callback | `[]` | — |
| 2 | Plugin calls X | `[x]` | — |
| ... | ... | ... | ... |
See [`code-trace-verification.md`](code-trace-verification.md) for the full format and a worked example.
Anti-patterns to avoid:
- **"I think the function does X"** — unacceptable. Read the code and cite the line.
- **"This is probably similar to upstream"** — check the fork commit pin.
- **"The drain loop terminates because of course it does"** — verify the actual loop condition.
- **Reasoning from the bug report without reading the code** — the report describes symptoms, not causes.
## 4. Synthesize root cause
Combine worker findings into a numbered call-chain trace. For each hop:
- The function name.
- The file:line.
- What state it reads/writes (e.g., `current_context_` value, queue contents).
End with a one-sentence root cause: "X happens because Y, which is unguarded because Z."
## 5. Fix design + side effects
Propose a fix primitive. For each candidate:
- **What it changes**: the specific code transformation.
- **Why it fixes the bug**: the causal chain.
- **Side effects**: what else it touches. Be honest — even a 5-line fix can have ABI or lifecycle implications.
- **Coverage**: does it also cover related bug classes? (E.g., drain-to-local covers both CPU-spin and stage-mismatch.)
If a fix has unacceptable side effects, document it as "deferred" and explain why. Don't sneak a behavior-change through as a "bug fix".
## 6. Cross-check related bug classes
Before declaring done, check: does the fix also cover bug classes that share the same machinery?
- Look at upstream issues in `proxy-wasm/proxy-wasm-cpp-host` that reference the same functions.
- Look at the Higress fork's divergence from upstream — are there Higress-specific code paths that have the same bug?
- Look at the `addAfterVmCallAction` callsites in envoy — are there other self-reenqueueing or cross-stage patterns?
Document the coverage explicitly: "Fixes X. Does NOT fix Y because Z. Y tracked separately as ..."
## 7. Verify when challenged
If the maintainer pushes back on a conclusion, **do not just restate the conclusion**. Re-read the code with their specific objection in mind.
Common failure modes in the first analysis round:
- **Confusing "state before operation" with "state after operation"**. (E.g., "the queue is `[X]`" — but is that before or after the swap?)
- **Assuming a function is sync/async without checking**. (E.g., assuming `sendLocalReply` is direct when it's actually deferred via `addAfterVmCallAction`.)
- **Reasoning about the patched code as if it were the unpatched code**. (E.g., analyzing Layer A's `doAfterVmCallActions` as if it still iterates the member queue in place, when actually it swaps to a local first.)
- **Assuming plugin call order**. (E.g., assuming `ResumeHttpRequest` is called before `SendHttpResponse` — verify from the reproducer.)
When you re-verify, dispatch a fresh worker with the specific objection. The worker should be told: "the maintainer claims X; verify whether X is true with code evidence." This avoids confirmation bias.
## 8. Document the pattern
After the investigation concludes, add a pattern file to `patterns/` capturing:
- The bug class (one-line summary).
- The root cause (with file:line evidence).
- The fix primitive (with the code snippet).
- The meta-lesson (what tripped up the analysis, what the maintainer caught, what generalizes).
This is what makes the skill accumulate value over time. See [`patterns/README.md`](../patterns/README.md) for the format.

View File

@@ -0,0 +1,64 @@
# Code-Trace Table Template
Use this template for the state-trace table that communicates queue/state transitions to maintainers. This format was developed during the #4034 investigation and proved effective for settling disagreements about program state at specific moments.
## When to use
- The bug involves a sequence of state changes (queue operations, `current_context_` saves/restores, reference-count transitions).
- You need to communicate "what the program state is at moment X" to someone who wasn't in your head.
- A maintainer is challenging a conclusion about state at a specific point in the call chain.
## Template
```markdown
| Step | Action | `<state variable 1>` | `<state variable 2>` | ... |
| --- | --- | --- | --- | ... |
| 1 | <entry point> | `<initial value>` | `<initial value>` | ... |
| 2 | <operation> | `<value after>` | `<value after>` | ... |
| 3 | <operation> | `<value after>` | `<value after>` | ... |
| ... | ... | ... | ... | ... |
| N ★ | <critical transition> | `<value>` | `<value>` | ... |
```
## Rules
1. **One row per discrete state change.** Don't combine multiple operations into one row.
2. **State columns are the variables that matter for the bug.** Common ones for Wasm bugs:
- `self->after_vm_call_actions_` (the deferred-action queue)
- `current_context_` (the thread_local ContextBase*)
- `SaveRestoreContext` depth (how many nested VM frames)
- `local` (the outer drain's swap target, post-Layer-A)
3. **Mark the critical transition with ★** and explain it below the table. This is the moment where the bug happens (or doesn't).
4. **Show the queue contents explicitly**`[sendLocalReply, continueDecoding]`, not "non-empty". Order matters for FIFO drains.
5. **If state is unknown / depends on plugin behavior, say so.** Don't fake precision. Better: "depends on whether the plugin called X before Y; both branches shown below".
6. **Pair the table with a prose explanation of the ★ transition.** The table shows WHAT; the prose explains WHY.
## Worked example
From #4034 — the table that settled whether Layer A's swap-to-local covers #326:
```markdown
| Step | Action | `self->after_vm_call_actions_` | outer drain's `local` |
| --- | --- | --- | --- |
| 1 | Enter `onHttpCallResponse` | `[]` | — |
| 2 | Plugin calls `SendHttpResponse` | `[sendLocalReply]` | — |
| 3 | Plugin calls `ResumeHttpRequest` | `[sendLocalReply, continueDecoding]` | — |
| 4 | Outer `~DeferAfterCallActions` fires | `[sendLocalReply, continueDecoding]` | — |
| 5 ★ | **Layer A: `local.swap(after_vm_call_actions_)`** | `[]` | `[sendLocalReply, continueDecoding]` |
| 6 | iter 1: fire `sendLocalReply``encodeHeaders``onResponseHeaders` | `[]` | `[sendLocalReply, continueDecoding]` |
| 7 | Inner `~DeferAfterCallActions` fires | `[]` (empty → no-op) | `[sendLocalReply, continueDecoding]` |
| 8 | iter 2: fire `continueDecoding` in outer context | `[]` | (iterating) |
```
**Why the ★ matters**: `std::deque::swap` is O(1) whole-content exchange. After step 5, the member queue is empty — it's NOT `[continueDecoding]` anymore. So when step 7's inner drain checks `if (!after_vm_call_actions_.empty())`, it sees `false` and returns. `continueDecoding` stays in the outer drain's `local` and fires in step 8, in the outer drain's context (correct phase).
The earlier (wrong) analysis had assumed the member queue still contained `continueDecoding` at step 7 — confusing "queue before swap" with "queue after swap".
## Anti-patterns
Don't do these:
- **Combining steps**: "steps 5-8: drain happens, continueDecoding fires" — too coarse; the reader can't see the state at each moment.
- **Hiding the critical transition**: burying the ★ in a paragraph of prose. The table IS the argument; make it sharp.
- **Vague state**: "queue has some items" — which items? In what order? FIFO drains care about order.
- **No prose explanation**: the table alone isn't enough; pair it with 2-3 sentences explaining the ★.

View File

@@ -0,0 +1,109 @@
# Investigation Report Template
Use this structure when starting a new deep investigation. Fill in the brackets; remove sections that don't apply. The goal is a report that a maintainer can audit end-to-end: every claim has a citation, every fix has a side-effect analysis.
---
# Investigation: <one-line bug summary>
> Source: #ISSUE_NUMBER
> Investigator: <name / agent session>
> Date: YYYY-MM-DD
> Status: <in-progress / root-caused / fix-designed / fixed>
## Symptom
<What observable behavior is wrong? Include:>
- **Observable**: <what the user sees — CPU spin, crash, wrong response, hang>
- **Trigger conditions**: <minimal repro path>
- **perf / stack data**: <paste perf sampling, stack trace, or relevant telemetry>
- **Blast radius**: <which versions, which components, how many users>
## Code surfaces
<Which repos and files are involved? Use a table:>
| Concern | Repo | File | Why it matters |
| --- | --- | --- | --- |
| <concern> | <repo URL> | <path:line> | <one-line role> |
<For Wasm bugs, always check the envoy fork's `bazel/repository_locations.bzl` for the pinned proxy-wasm-cpp-host commit. Note it here.>
## Worker dispatch
<If you dispatched parallel workers, list them with their brief and key finding:>
- **Worker A (<repo>) — <one-line brief>**: <VERDICT — one-line finding>. Key evidence: <file:line>.
- **Worker B (<repo>) — <one-line brief>**: ...
<If the investigation was single-threaded (no workers), say so and explain why.>
## Root cause
<One-paragraph explanation. Be specific about the mechanism.>
<Numbered call-chain trace, one hop per line:>
1. <Function> (`repo file.cc:NN`) — <what it does>
2. <Function> (`repo file.cc:NN`) — <what it does>
3. ...
<For queue/state bugs, include a state-trace table here. See `code-trace-verification.md` for the format.>
**Root cause (one sentence)**: <X happens because Y, which is unguarded because Z>.
## Fix
<The primitive. Code snippet if non-trivial.>
```cpp
// Before
<current code>
// After
<fixed code>
```
**Why it fixes the bug**: <causal chain, 2-3 sentences.>
**Side effects**: <what else the change touches. Be honest — even a 5-line fix can have ABI or lifecycle implications. If there are none, say "none identified" and explain the verification.>
## Coverage analysis
<Does the fix cover related bug classes? Check upstream issues, related callbacks, similar patterns.>
| Bug class | Covered? | Why |
| --- | --- | --- |
| <primary bug> | yes | <mechanism> |
| <related class 1> | yes/no | <mechanism or reason> |
| <related class 2> | yes/no | ... |
<For each "no", note where it's tracked (separate issue, deferred SPEC, etc.).>
## Evidence index
<All file:line citations grouped by claim. This is the audit trail.>
| Claim | Citation |
| --- | --- |
| <claim> | `repo/path/file.cc:NN` |
| ... | ... |
## Open questions
<Any unresolved questions for the maintainer. Each should be specific and actionable.>
- **Q1**: <question>
- **Q2**: <question>
## Next steps
<What happens next. Be concrete:>
- [ ] <action 1>
- [ ] <action 2>
---
<After the investigation concludes, distill the meta-lessons and add a pattern file to `patterns/`. See `patterns/README.md` for the format.>

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:

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 ?= fcc50202f47e27f6b8391a4bd9bbc0a9127d89d7
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))
}

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