mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 21:21:01 +08:00
Compare commits
37 Commits
v1.3.6
...
v1.4.0-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78418b50ff | ||
|
|
7fcb608fce | ||
|
|
10f1adc730 | ||
|
|
e4d535ea65 | ||
|
|
76b5f2af79 | ||
|
|
fc6a6aad89 | ||
|
|
af8eff2bd6 | ||
|
|
d91b22f8c2 | ||
|
|
f4a73b986c | ||
|
|
bff21b2307 | ||
|
|
33013d07f4 | ||
|
|
22a3e7018b | ||
|
|
2ff56c82f8 | ||
|
|
9b50343618 | ||
|
|
f9994237d1 | ||
|
|
ae54318557 | ||
|
|
0ec6719751 | ||
|
|
dfa1be3b47 | ||
|
|
95aa69cb95 | ||
|
|
5333031f31 | ||
|
|
31242c36ba | ||
|
|
3119ec8e24 | ||
|
|
42c9c3d824 | ||
|
|
8736188e6a | ||
|
|
559a109ae5 | ||
|
|
8043780de0 | ||
|
|
333f9b48f3 | ||
|
|
5c7736980c | ||
|
|
2031c659c2 | ||
|
|
03d2f01274 | ||
|
|
6577ae8822 | ||
|
|
a8c74c8302 | ||
|
|
a787088c0e | ||
|
|
e68b5c86c4 | ||
|
|
5fec6e9ab7 | ||
|
|
3b2196d0f8 | ||
|
|
a592b2b103 |
1
.github/workflows/build-and-test-plugin.yaml
vendored
1
.github/workflows/build-and-test-plugin.yaml
vendored
@@ -11,6 +11,7 @@ on:
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'test/**'
|
||||
workflow_dispatch: ~
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/envoy @gengleilei @johnlanni
|
||||
/istio @SpecialYang @johnlanni
|
||||
/pkg @SpecialYang @johnlanni @CH3CHO
|
||||
/plugins @johnlanni @WeixinX
|
||||
/plugins @johnlanni @WeixinX @CH3CHO
|
||||
/registry @NameHaibinZhang @2456868764 @johnlanni
|
||||
/test @Xunzhuo @2456868764 @CH3CHO
|
||||
/tools @johnlanni @Xunzhuo @2456868764
|
||||
|
||||
@@ -138,11 +138,11 @@ export ENVOY_TAR_PATH:=/home/package/envoy.tar.gz
|
||||
|
||||
external/package/envoy-amd64.tar.gz:
|
||||
# cd external/proxy; BUILD_WITH_CONTAINER=1 make test_release
|
||||
cd external/package; wget -O envoy-amd64.tar.gz "https://github.com/alibaba/higress/releases/download/v1.3.4-rc.1/envoy-symbol-amd64.tar.gz"
|
||||
cd external/package; wget -O envoy-amd64.tar.gz "https://github.com/alibaba/higress/releases/download/v1.4.0-rc.1/envoy-symbol-amd64.tar.gz"
|
||||
|
||||
external/package/envoy-arm64.tar.gz:
|
||||
# cd external/proxy; BUILD_WITH_CONTAINER=1 make test_release
|
||||
cd external/package; wget -O envoy-arm64.tar.gz "https://github.com/alibaba/higress/releases/download/v1.3.4-rc.1/envoy-symbol-arm64.tar.gz"
|
||||
cd external/package; wget -O envoy-arm64.tar.gz "https://github.com/alibaba/higress/releases/download/v1.4.0-rc.1/envoy-symbol-arm64.tar.gz"
|
||||
|
||||
build-pilot:
|
||||
cd external/istio; rm -rf out/linux_amd64; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 make build-linux
|
||||
@@ -177,8 +177,8 @@ install: pre-install
|
||||
cd helm/higress; helm dependency build
|
||||
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
|
||||
|
||||
ENVOY_LATEST_IMAGE_TAG ?= sha-29baf85
|
||||
ISTIO_LATEST_IMAGE_TAG ?= sha-29baf85
|
||||
ENVOY_LATEST_IMAGE_TAG ?= sha-d91b22f
|
||||
ISTIO_LATEST_IMAGE_TAG ?= sha-d91b22f
|
||||
|
||||
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'
|
||||
|
||||
5505
envoy/1.20/patches/envoy/20240519-wasm-upgrade.patch
Normal file
5505
envoy/1.20/patches/envoy/20240519-wasm-upgrade.patch
Normal file
File diff suppressed because it is too large
Load Diff
14
envoy/1.20/patches/envoy/20240521-fix-wasm-host.patch
Normal file
14
envoy/1.20/patches/envoy/20240521-fix-wasm-host.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
diff -Naur envoy/bazel/repository_locations.bzl envoy-new/bazel/repository_locations.bzl
|
||||
--- envoy/bazel/repository_locations.bzl 2024-05-21 22:49:46.686598518 +0800
|
||||
+++ envoy-new/bazel/repository_locations.bzl 2024-05-21 22:49:02.554597652 +0800
|
||||
@@ -1031,8 +1031,8 @@
|
||||
project_name = "WebAssembly for Proxies (C++ host implementation)",
|
||||
project_desc = "WebAssembly for Proxies (C++ host implementation)",
|
||||
project_url = "https://github.com/higress-group/proxy-wasm-cpp-host",
|
||||
- version = "f8b624dc6c37d4e0a3c1b332652746793e2031ad",
|
||||
- sha256 = "ba20328101c91d0ae6383947ced99620cd9b4ea22ab2fda6b26f343b38c3be83",
|
||||
+ version = "cad2eb04d402dbf559101f3cb4f44da0d9c5b0b0",
|
||||
+ sha256 = "4efbcc97c58994fab92c9dc50c051ad16463647d4c0c6df36a7204d2984c1e63",
|
||||
strip_prefix = "proxy-wasm-cpp-host-{version}",
|
||||
urls = ["https://github.com/higress-group/proxy-wasm-cpp-host/archive/{version}.tar.gz"],
|
||||
use_category = ["dataplane_ext"],
|
||||
35
go.mod
35
go.mod
@@ -44,13 +44,13 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.8.3
|
||||
go.uber.org/atomic v1.9.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
google.golang.org/grpc v1.48.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
istio.io/api v0.0.0-20211122181927-8da52c66ff23
|
||||
istio.io/client-go v1.12.0-rc.1.0.20211118171212-b744b6f111e4
|
||||
istio.io/client-go v1.12.0-rc.1.0.20211118171212-b744b6f111e4 // indirect
|
||||
istio.io/gogo-genproto v0.0.0-20211115195057-0e34bdd2be67
|
||||
istio.io/istio v0.0.0
|
||||
istio.io/pkg v0.0.0-20211115195056-e379f31ee62a
|
||||
@@ -172,6 +172,7 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
@@ -185,6 +186,7 @@ require (
|
||||
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect
|
||||
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
|
||||
github.com/lib/pq v1.10.0 // indirect
|
||||
github.com/libdns/libdns v0.2.1 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
@@ -194,7 +196,7 @@ require (
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/miekg/dns v1.1.43 // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
|
||||
@@ -248,20 +250,23 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
|
||||
github.com/yl2chen/cidranger v1.0.2 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
|
||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/oauth2 v0.6.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v3 v3.0.1 // indirect
|
||||
gomodules.xyz/orderedmap v0.1.0 // indirect
|
||||
@@ -276,8 +281,6 @@ require (
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
k8s.io/apiserver v0.22.5 // indirect
|
||||
k8s.io/component-base v0.22.5 // indirect
|
||||
k8s.io/klog/v2 v2.60.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c // indirect
|
||||
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
|
||||
oras.land/oras-go v0.4.0 // indirect
|
||||
@@ -300,13 +303,19 @@ replace istio.io/client-go => ./external/client-go
|
||||
|
||||
replace istio.io/istio => ./external/istio
|
||||
|
||||
replace github.com/caddyserver/certmagic => github.com/2456868764/certmagic v1.0.1
|
||||
|
||||
require (
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/evanphx/json-patch/v5 v5.6.0
|
||||
github.com/google/yamlfmt v0.10.0
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/mholt/acmez v1.2.0
|
||||
github.com/tidwall/gjson v1.17.0
|
||||
helm.sh/helm/v3 v3.7.1
|
||||
k8s.io/apiextensions-apiserver v0.25.4
|
||||
k8s.io/component-base v0.22.5
|
||||
k8s.io/klog/v2 v2.60.1
|
||||
knative.dev/networking v0.0.0-20220302134042-e8b2eb995165
|
||||
knative.dev/pkg v0.0.0-20220301181942-2fdd5f232e77
|
||||
)
|
||||
|
||||
58
go.sum
58
go.sum
@@ -61,6 +61,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/2456868764/certmagic v1.0.1 h1:dRzow2Npe9llFTBhNVl0fVe8Yi/Q14ygNonlaZUyDZQ=
|
||||
github.com/2456868764/certmagic v1.0.1/go.mod h1:LOn81EQYMPajdew6Ln6SVdHPxPqPv6jwsUg92kiNlcQ=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210929163055-e81b3f25be97/go.mod h1:WpB7kf89yJUETZxQnP1kgYPNwlT2jjdDYUCoxVggM3g=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||
@@ -1006,6 +1008,9 @@ github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -1055,6 +1060,8 @@ github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTRe
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
@@ -1145,13 +1152,16 @@ github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqA
|
||||
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.17/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
|
||||
@@ -1658,6 +1668,12 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go
|
||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
|
||||
github.com/zclconf/go-cty v1.7.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
@@ -1711,8 +1727,9 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
@@ -1722,8 +1739,9 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
@@ -1733,8 +1751,9 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -1775,8 +1794,8 @@ golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1817,7 +1836,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1894,8 +1914,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -1932,8 +1952,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -2074,15 +2094,16 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -2092,8 +2113,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -2182,7 +2203,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 1.3.6
|
||||
appVersion: 1.4.0-rc.1
|
||||
description: Helm chart for deploying higress gateways
|
||||
icon: https://higress.io/img/higress_logo_small.png
|
||||
home: http://higress.io/
|
||||
@@ -10,4 +10,4 @@ name: higress-core
|
||||
sources:
|
||||
- http://github.com/alibaba/higress
|
||||
type: application
|
||||
version: 1.3.6
|
||||
version: 1.4.0-rc.1
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
# Refer to https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain
|
||||
trustDomain: "cluster.local"
|
||||
accessLogEncoding: TEXT
|
||||
{{- if .Values.global.o11y.enabled }}
|
||||
accessLogFile: "/var/log/proxy/access.log"
|
||||
{{- else }}
|
||||
accessLogFile: "/dev/stdout"
|
||||
{{- end }}
|
||||
ingressControllerMode: "OFF"
|
||||
accessLogFormat: '{"authority":"%REQ(:AUTHORITY)%","bytes_received":"%BYTES_RECEIVED%","bytes_sent":"%BYTES_SENT%","downstream_local_address":"%DOWNSTREAM_LOCAL_ADDRESS%","downstream_remote_address":"%DOWNSTREAM_REMOTE_ADDRESS%","duration":"%DURATION%","istio_policy_status":"%DYNAMIC_METADATA(istio.mixer:status)%","method":"%REQ(:METHOD)%","path":"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%","protocol":"%PROTOCOL%","request_id":"%REQ(X-REQUEST-ID)%","requested_server_name":"%REQUESTED_SERVER_NAME%","response_code":"%RESPONSE_CODE%","response_flags":"%RESPONSE_FLAGS%","route_name":"%ROUTE_NAME%","start_time":"%START_TIME%","trace_id":"%REQ(X-B3-TRACEID)%","upstream_cluster":"%UPSTREAM_CLUSTER%","upstream_host":"%UPSTREAM_HOST%","upstream_local_address":"%UPSTREAM_LOCAL_ADDRESS%","upstream_service_time":"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%","upstream_transport_failure_reason":"%UPSTREAM_TRANSPORT_FAILURE_REASON%","user_agent":"%REQ(USER-AGENT)%","x_forwarded_for":"%REQ(X-FORWARDED-FOR)%"}
|
||||
accessLogFormat: '{"authority":"%REQ(X-ENVOY-ORIGINAL-HOST?:AUTHORITY)%","bytes_received":"%BYTES_RECEIVED%","bytes_sent":"%BYTES_SENT%","downstream_local_address":"%DOWNSTREAM_LOCAL_ADDRESS%","downstream_remote_address":"%DOWNSTREAM_REMOTE_ADDRESS%","duration":"%DURATION%","istio_policy_status":"%DYNAMIC_METADATA(istio.mixer:status)%","method":"%REQ(:METHOD)%","path":"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%","protocol":"%PROTOCOL%","request_id":"%REQ(X-REQUEST-ID)%","requested_server_name":"%REQUESTED_SERVER_NAME%","response_code":"%RESPONSE_CODE%","response_flags":"%RESPONSE_FLAGS%","route_name":"%ROUTE_NAME%","start_time":"%START_TIME%","trace_id":"%REQ(X-B3-TRACEID)%","upstream_cluster":"%UPSTREAM_CLUSTER%","upstream_host":"%UPSTREAM_HOST%","upstream_local_address":"%UPSTREAM_LOCAL_ADDRESS%","upstream_service_time":"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%","upstream_transport_failure_reason":"%UPSTREAM_TRANSPORT_FAILURE_REASON%","user_agent":"%REQ(USER-AGENT)%","x_forwarded_for":"%REQ(X-FORWARDED-FOR)%"}
|
||||
|
||||
'
|
||||
dnsRefreshRate: 200s
|
||||
|
||||
@@ -70,6 +70,8 @@ spec:
|
||||
periodSeconds: 3
|
||||
timeoutSeconds: 5
|
||||
env:
|
||||
- name: PILOT_ENABLE_HEADLESS_SERVICE_POD_LISTENERS
|
||||
value: "false"
|
||||
- name: HIGRESS_SYSTEM_NS
|
||||
value: "{{ .Release.Namespace }}"
|
||||
- name: DEFAULT_UPSTREAM_CONCURRENCY_THRESHOLD
|
||||
@@ -206,6 +208,8 @@ spec:
|
||||
{{- if .Values.global.watchNamespace }}
|
||||
- --watchNamespace={{ .Values.global.watchNamespace }}
|
||||
{{- end }}
|
||||
- --enableAutomaticHttps={{ .Values.controller.automaticHttps.enabled }}
|
||||
- --automaticHttpsEmail={{ .Values.controller.automaticHttps.email }}
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{{- $o11y := .Values.global.o11y }}
|
||||
{{- $unprivilegedPortSupported := true }}
|
||||
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
|
||||
{{- $kernelVersion := $node.status.nodeInfo.kernelVersion }}
|
||||
@@ -67,6 +68,40 @@ spec:
|
||||
value: "0"
|
||||
{{- end }}
|
||||
containers:
|
||||
{{- if $o11y.enabled }}
|
||||
{{- $config := $o11y.promtail }}
|
||||
- name: promtail
|
||||
image: {{ $config.image.repository }}:{{ $config.image.tag }}
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- -config.file=/etc/promtail/promtail.yaml
|
||||
env:
|
||||
- name: 'HOSTNAME'
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: 'spec.nodeName'
|
||||
ports:
|
||||
- containerPort: {{ $config.port }}
|
||||
name: http-metrics
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: {{ $config.port }}
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
volumeMounts:
|
||||
- name: promtail-config
|
||||
mountPath: "/etc/promtail"
|
||||
- name: log
|
||||
mountPath: /var/log/proxy
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
{{- end }}
|
||||
- name: higress-gateway
|
||||
image: "{{ .Values.gateway.hub | default .Values.global.hub }}/{{ .Values.gateway.image | default "gateway" }}:{{ .Values.gateway.tag | default .Chart.AppVersion }}"
|
||||
args:
|
||||
@@ -88,7 +123,10 @@ spec:
|
||||
- ALL
|
||||
allowPrivilegeEscalation: false
|
||||
privileged: false
|
||||
# When enabling lite metrics, the configuration template files need to be replaced.
|
||||
{{- if not .Values.global.liteMetrics }}
|
||||
readOnlyRootFilesystem: true
|
||||
{{- end }}
|
||||
runAsUser: 1337
|
||||
runAsGroup: 1337
|
||||
runAsNonRoot: true
|
||||
@@ -102,7 +140,6 @@ spec:
|
||||
runAsGroup: 1337
|
||||
runAsNonRoot: false
|
||||
allowPrivilegeEscalation: true
|
||||
readOnlyRootFilesystem: true
|
||||
{{- end }}
|
||||
env:
|
||||
- name: NODE_NAME
|
||||
@@ -148,6 +185,10 @@ spec:
|
||||
value: "{{ $.Values.clusterName | default `Kubernetes` }}"
|
||||
- name: INSTANCE_NAME
|
||||
value: "higress-gateway"
|
||||
{{- if .Values.global.liteMetrics }}
|
||||
- name: LITE_METRICS
|
||||
value: "on"
|
||||
{{- end }}
|
||||
{{- if include "skywalking.enabled" . }}
|
||||
- name: ISTIO_BOOTSTRAP_OVERRIDE
|
||||
value: /etc/istio/custom-bootstrap/custom_bootstrap.json
|
||||
@@ -212,6 +253,10 @@ spec:
|
||||
- mountPath: /opt/plugins
|
||||
name: local-wasmplugins-volume
|
||||
{{- end }}
|
||||
{{- if $o11y.enabled }}
|
||||
- mountPath: /var/log/proxy
|
||||
name: log
|
||||
{{- end }}
|
||||
{{- if .Values.gateway.hostNetwork }}
|
||||
hostNetwork: {{ .Values.gateway.hostNetwork }}
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
@@ -258,6 +303,15 @@ spec:
|
||||
emptyDir: {}
|
||||
- name: proxy-socket
|
||||
emptyDir: {}
|
||||
{{- if $o11y.enabled }}
|
||||
- name: log
|
||||
emptyDir: {}
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
- name: promtail-config
|
||||
configMap:
|
||||
name: higress-promtail
|
||||
{{- end }}
|
||||
- name: podinfo
|
||||
downwardAPI:
|
||||
defaultMode: 420
|
||||
|
||||
64
helm/core/templates/promtail.yaml
Normal file
64
helm/core/templates/promtail.yaml
Normal file
@@ -0,0 +1,64 @@
|
||||
{{- $o11y := .Values.global.o11y }}
|
||||
{{- if $o11y.enabled }}
|
||||
{{- $config := $o11y.promtail }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: higress-promtail
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
promtail.yaml: |
|
||||
server:
|
||||
log_level: info
|
||||
http_listen_port: {{ $config.port }}
|
||||
|
||||
clients:
|
||||
- url: http://higress-console-loki.{{ .Release.Namespace }}:3100/loki/api/v1/push
|
||||
|
||||
positions:
|
||||
filename: /tmp/promtail-positions.yaml
|
||||
target_config:
|
||||
sync_period: 10s
|
||||
scrape_configs:
|
||||
- job_name: access-logs
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
__path__: /var/log/proxy/access.log
|
||||
pipeline_stages:
|
||||
- json:
|
||||
expressions:
|
||||
authority:
|
||||
method:
|
||||
path:
|
||||
protocol:
|
||||
request_id:
|
||||
response_code:
|
||||
response_flags:
|
||||
route_name:
|
||||
trace_id:
|
||||
upstream_cluster:
|
||||
upstream_host:
|
||||
upstream_transport_failure_reason:
|
||||
user_agent:
|
||||
x_forwarded_for:
|
||||
- labels:
|
||||
authority:
|
||||
method:
|
||||
path:
|
||||
protocol:
|
||||
request_id:
|
||||
response_code:
|
||||
response_flags:
|
||||
route_name:
|
||||
trace_id:
|
||||
upstream_cluster:
|
||||
upstream_host:
|
||||
upstream_transport_failure_reason:
|
||||
user_agent:
|
||||
x_forwarded_for:
|
||||
- timestamp:
|
||||
source: timestamp
|
||||
format: RFC3339Nano
|
||||
{{- end }}
|
||||
@@ -1,6 +1,7 @@
|
||||
revision: ""
|
||||
global:
|
||||
xdsMaxRecvMsgSize: 104857600
|
||||
liteMetrics: false
|
||||
xdsMaxRecvMsgSize: "104857600"
|
||||
defaultUpstreamConcurrencyThreshold: 10000
|
||||
enableSRDS: true
|
||||
onDemandRDS: false
|
||||
@@ -337,6 +338,20 @@ global:
|
||||
# Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source.
|
||||
useMCP: false
|
||||
|
||||
# Observability (o11y) configurations
|
||||
o11y:
|
||||
enabled: false
|
||||
promtail:
|
||||
image:
|
||||
repository: grafana/promtail
|
||||
tag: 2.9.4
|
||||
port: 3101
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 2Gi
|
||||
securityContext: {}
|
||||
|
||||
# The name of the CA for workload certificates.
|
||||
# For example, when caName=GkeWorkloadCertificate, GKE workload certificates
|
||||
# will be used as the certificates for workloads.
|
||||
@@ -529,6 +544,12 @@ controller:
|
||||
"port": 8888,
|
||||
"targetPort": 8888,
|
||||
},
|
||||
{
|
||||
"name": "http-solver",
|
||||
"protocol": "TCP",
|
||||
"port": 8889,
|
||||
"targetPort": 8889,
|
||||
},
|
||||
{
|
||||
"name": "grpc",
|
||||
"protocol": "TCP",
|
||||
@@ -567,6 +588,9 @@ controller:
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
targetCPUUtilizationPercentage: 80
|
||||
automaticHttps:
|
||||
enabled: false
|
||||
email: ""
|
||||
|
||||
## Discovery Settings
|
||||
pilot:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
dependencies:
|
||||
- name: higress-core
|
||||
repository: file://../core
|
||||
version: 1.3.6
|
||||
version: 1.4.0-rc.1
|
||||
- name: higress-console
|
||||
repository: https://higress.io/helm-charts/
|
||||
version: 1.3.3
|
||||
digest: sha256:6bf02020df81c81fedf69976ba0e2a2620527b7cbd11d7602e5e6ae3427b959f
|
||||
generated: "2024-04-22T19:32:07.927664+08:00"
|
||||
version: 1.4.0
|
||||
digest: sha256:320b1b3ed08fad56dff0d21faaffe41a0325fdcdb96847e53a588d6b0df7e73e
|
||||
generated: "2024-05-19T17:52:19.676747+08:00"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 1.3.6
|
||||
appVersion: 1.4.0-rc.1
|
||||
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: 1.3.6
|
||||
version: 1.4.0-rc.1
|
||||
- name: higress-console
|
||||
repository: "https://higress.io/helm-charts/"
|
||||
version: 1.3.3
|
||||
version: 1.4.0
|
||||
type: application
|
||||
version: 1.3.6
|
||||
version: 1.4.0-rc.1
|
||||
|
||||
83
istio/1.12/patches/istio/20240518-optimize-rds-cache.patch
Normal file
83
istio/1.12/patches/istio/20240518-optimize-rds-cache.patch
Normal file
@@ -0,0 +1,83 @@
|
||||
diff -Naur istio/pilot/pkg/networking/core/v1alpha3/gateway.go istio-new/pilot/pkg/networking/core/v1alpha3/gateway.go
|
||||
--- istio/pilot/pkg/networking/core/v1alpha3/gateway.go 2024-05-18 19:09:14.000000000 +0800
|
||||
+++ istio-new/pilot/pkg/networking/core/v1alpha3/gateway.go 2024-05-18 18:08:30.000000000 +0800
|
||||
@@ -457,8 +457,46 @@
|
||||
hostVs := push.VirtualServicesForHost(node, hostRDSHost)
|
||||
|
||||
var httpRoutes []config.Config
|
||||
+ var vsDependent []config.Config
|
||||
+
|
||||
+ cacheable := true
|
||||
|
||||
for _, vs := range hostVs {
|
||||
+ vsSpec := vs.Spec.(*networking.VirtualService)
|
||||
+ for _, vsHttpRoute := range vsSpec.Http {
|
||||
+ // check if dynamic port exists, we should not cache RDS
|
||||
+ for _, vsRoute := range vsHttpRoute.Route {
|
||||
+ if vsRoute.Destination.Port == nil {
|
||||
+ cacheable = false
|
||||
+ }
|
||||
+ for _, fallbackDestination := range vsRoute.FallbackClusters {
|
||||
+ if fallbackDestination.Port == nil {
|
||||
+ cacheable = false
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ if vsHttpRoute.Mirror != nil && vsHttpRoute.Mirror.Port == nil {
|
||||
+ cacheable = false
|
||||
+ }
|
||||
+ if vsHttpRoute.Delegate != nil {
|
||||
+ vsDependent = append(vsDependent, config.Config{
|
||||
+ Meta: config.Meta{
|
||||
+ GroupVersionKind: gvk.VirtualService,
|
||||
+ Name: vsHttpRoute.Delegate.Name,
|
||||
+ Namespace: vsHttpRoute.Delegate.Namespace,
|
||||
+ },
|
||||
+ Spec: networking.VirtualService{},
|
||||
+ })
|
||||
+ }
|
||||
+ }
|
||||
+ vsDependent = append(vsDependent, config.Config{
|
||||
+ Meta: config.Meta{
|
||||
+ GroupVersionKind: gvk.VirtualService,
|
||||
+ Name: vs.Name,
|
||||
+ Namespace: vs.Namespace,
|
||||
+ },
|
||||
+ Spec: vs.Spec,
|
||||
+ })
|
||||
if len(vs.Annotations) == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -489,14 +527,19 @@
|
||||
ProxyVersion: node.Metadata.IstioVersion,
|
||||
ListenerPort: rdsPort,
|
||||
// Use same host vs to cache, although the cache can be cleared when the port is different, this can be accepted
|
||||
- VirtualServices: hostVs,
|
||||
+ VirtualServices: vsDependent,
|
||||
HTTPRoutes: httpRoutes,
|
||||
EnvoyFilterKeys: efKeys,
|
||||
}
|
||||
|
||||
- resource, exist := configgen.Cache.Get(routeCache)
|
||||
- if exist {
|
||||
- return resource, true
|
||||
+ var resource *discovery.Resource
|
||||
+ if cacheable {
|
||||
+ resource, exist := configgen.Cache.Get(routeCache)
|
||||
+ if exist {
|
||||
+ return resource, true
|
||||
+ }
|
||||
+ } else {
|
||||
+ log.Warnf("route cache is disabled for RDS:%s", routeName)
|
||||
}
|
||||
|
||||
listenerPort := uint32(rdsPort)
|
||||
@@ -727,7 +770,7 @@
|
||||
Resource: util.MessageToAny(routeCfg),
|
||||
}
|
||||
|
||||
- if features.EnableRDSCaching {
|
||||
+ if features.EnableRDSCaching && cacheable {
|
||||
configgen.Cache.Add(routeCache, req, resource)
|
||||
}
|
||||
|
||||
752
istio/1.12/patches/istio/20240519-proxy-start-script.patch
Normal file
752
istio/1.12/patches/istio/20240519-proxy-start-script.patch
Normal file
@@ -0,0 +1,752 @@
|
||||
diff -Naur istio/pilot/docker/Dockerfile.proxyv2 istio-new/pilot/docker/Dockerfile.proxyv2
|
||||
--- istio/pilot/docker/Dockerfile.proxyv2 2024-05-19 16:40:42.706769894 +0800
|
||||
+++ istio-new/pilot/docker/Dockerfile.proxyv2 2024-05-19 16:07:20.630730574 +0800
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
# Copy Envoy bootstrap templates used by pilot-agent
|
||||
COPY envoy_bootstrap.json /var/lib/istio/envoy/envoy_bootstrap_tmpl.json
|
||||
+COPY envoy_bootstrap_lite.json /var/lib/istio/envoy/envoy_bootstrap_lite_tmpl.json
|
||||
COPY gcp_envoy_bootstrap.json /var/lib/istio/envoy/gcp_envoy_bootstrap_tmpl.json
|
||||
|
||||
# Install Envoy.
|
||||
@@ -47,5 +48,30 @@
|
||||
# COPY metadata-exchange-filter.wasm /etc/istio/extensions/metadata-exchange-filter.wasm
|
||||
# COPY metadata-exchange-filter.compiled.wasm /etc/istio/extensions/metadata-exchange-filter.compiled.wasm
|
||||
|
||||
+RUN apt-get update && \
|
||||
+ apt-get install --no-install-recommends -y \
|
||||
+ logrotate \
|
||||
+ cron \
|
||||
+ && apt-get upgrade -y \
|
||||
+ && apt-get clean
|
||||
+
|
||||
+# Latest releases available at https://github.com/aptible/supercronic/releases
|
||||
+ENV SUPERCRONIC_URL=https://higress.io/release-binary/supercronic-linux-${TARGETARCH:-amd64} \
|
||||
+ SUPERCRONIC=supercronic-linux-${TARGETARCH:-amd64}
|
||||
+
|
||||
+RUN curl -fsSLO "$SUPERCRONIC_URL" \
|
||||
+ && chmod +x "$SUPERCRONIC" \
|
||||
+ && mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" \
|
||||
+ && ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic
|
||||
+
|
||||
+
|
||||
+COPY higress-proxy-start.sh /usr/local/bin/higress-proxy-start.sh
|
||||
+
|
||||
+COPY higress-proxy-container-init.sh /usr/local/bin/higress-proxy-container-init.sh
|
||||
+
|
||||
+RUN chmod a+x /usr/local/bin/higress-proxy-container-init.sh;/usr/local/bin/higress-proxy-container-init.sh
|
||||
+
|
||||
+RUN chmod a+x /usr/local/bin/higress-proxy-start.sh
|
||||
+
|
||||
# The pilot-agent will bootstrap Envoy.
|
||||
-ENTRYPOINT ["/usr/local/bin/pilot-agent"]
|
||||
+ENTRYPOINT ["/usr/local/bin/higress-proxy-start.sh"]
|
||||
diff -Naur istio/tools/istio-docker.mk istio-new/tools/istio-docker.mk
|
||||
--- istio/tools/istio-docker.mk 2024-05-19 16:40:42.734769895 +0800
|
||||
+++ istio-new/tools/istio-docker.mk 2024-05-19 16:02:43.222725126 +0800
|
||||
@@ -96,6 +96,9 @@
|
||||
docker.proxyv2: BUILD_ARGS=--build-arg proxy_version=istio-proxy:${PROXY_REPO_SHA} --build-arg istio_version=${VERSION} --build-arg BASE_VERSION=${BASE_VERSION} --build-arg SIDECAR=${SIDECAR} --build-arg HUB=${HUB}
|
||||
docker.proxyv2: ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/envoy_bootstrap.json
|
||||
docker.proxyv2: ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/gcp_envoy_bootstrap.json
|
||||
+docker.proxyv2: ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/higress-proxy-start.sh
|
||||
+docker.proxyv2: ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/higress-proxy-container-init.sh
|
||||
+docker.proxyv2: ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/envoy_bootstrap_lite.json
|
||||
docker.proxyv2: ${ISTIO_ENVOY_LINUX_ARM64_RELEASE_DIR}/${SIDECAR}
|
||||
docker.proxyv2: ${ISTIO_ENVOY_LINUX_AMD64_RELEASE_DIR}/${SIDECAR}
|
||||
docker.proxyv2: $(ISTIO_OUT_LINUX)/pilot-agent
|
||||
diff -Naur istio/tools/packaging/common/envoy_bootstrap_lite.json istio-new/tools/packaging/common/envoy_bootstrap_lite.json
|
||||
--- istio/tools/packaging/common/envoy_bootstrap_lite.json 1970-01-01 08:00:00.000000000 +0800
|
||||
+++ istio-new/tools/packaging/common/envoy_bootstrap_lite.json 2024-05-19 16:36:39.274765113 +0800
|
||||
@@ -0,0 +1,642 @@
|
||||
+{
|
||||
+ "node": {
|
||||
+ "id": "{{ .nodeID }}",
|
||||
+ "cluster": "{{ .cluster }}",
|
||||
+ "locality": {
|
||||
+ {{- if .region }}
|
||||
+ "region": "{{ .region }}"
|
||||
+ {{- end }}
|
||||
+ {{- if .zone }}
|
||||
+ {{- if .region }}
|
||||
+ ,
|
||||
+ {{- end }}
|
||||
+ "zone": "{{ .zone }}"
|
||||
+ {{- end }}
|
||||
+ {{- if .sub_zone }}
|
||||
+ {{- if or .region .zone }}
|
||||
+ ,
|
||||
+ {{- end }}
|
||||
+ "sub_zone": "{{ .sub_zone }}"
|
||||
+ {{- end }}
|
||||
+ },
|
||||
+ "metadata": {{ .meta_json_str }}
|
||||
+ },
|
||||
+ "layered_runtime": {
|
||||
+ "layers": [
|
||||
+ {
|
||||
+ "name": "global config",
|
||||
+ "static_layer": {{ .runtime_flags }}
|
||||
+ },
|
||||
+ {
|
||||
+ "name": "admin",
|
||||
+ "admin_layer": {}
|
||||
+ }
|
||||
+ ]
|
||||
+ },
|
||||
+ "stats_config": {
|
||||
+ "use_all_default_tags": false,
|
||||
+ "stats_tags": [
|
||||
+ {
|
||||
+ "tag_name": "response_code_class",
|
||||
+ "regex": "_rq(_(\\dxx))$"
|
||||
+ },
|
||||
+ {
|
||||
+ "tag_name": "listener_address",
|
||||
+ "regex": "^listener\\.(((?:[_.[:digit:]]*|[_\\[\\]aAbBcCdDeEfF[:digit:]]*))\\.)"
|
||||
+ },
|
||||
+ {
|
||||
+ "tag_name": "http_conn_manager_prefix",
|
||||
+ "regex": "^http\\.(((outbound_([0-9]{1,3}\\.{0,1}){4}_\\d{0,5})|([^\\.]+))\\.)"
|
||||
+ },
|
||||
+ {
|
||||
+ "tag_name": "cluster_name",
|
||||
+ "regex": "^cluster\\.((.*?)\\.)(http1\\.|http2\\.|health_check\\.|zone\\.|external\\.|circuit_breakers\\.|[^\\.]+$)"
|
||||
+ }
|
||||
+ ],
|
||||
+ "stats_matcher": {
|
||||
+ "exclusion_list": {
|
||||
+ "patterns": [
|
||||
+ {
|
||||
+ "prefix": "vhost"
|
||||
+ },
|
||||
+ {
|
||||
+ "safe_regex": {"regex": "^http.*rds.*", "google_re2":{}}
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ "admin": {
|
||||
+ "access_log_path": "/dev/null",
|
||||
+ "profile_path": "/var/lib/istio/data/envoy.prof",
|
||||
+ "address": {
|
||||
+ "socket_address": {
|
||||
+ "address": "{{ .localhost }}",
|
||||
+ "port_value": {{ .config.ProxyAdminPort }}
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ "dynamic_resources": {
|
||||
+ "lds_config": {
|
||||
+ "ads": {},
|
||||
+ "initial_fetch_timeout": "0s",
|
||||
+ "resource_api_version": "V3"
|
||||
+ },
|
||||
+ "cds_config": {
|
||||
+ "ads": {},
|
||||
+ "initial_fetch_timeout": "0s",
|
||||
+ "resource_api_version": "V3"
|
||||
+ },
|
||||
+ "ads_config": {
|
||||
+ "api_type": "{{ .xds_type }}",
|
||||
+ "set_node_on_first_message_only": true,
|
||||
+ "transport_api_version": "V3",
|
||||
+ "grpc_services": [
|
||||
+ {
|
||||
+ "envoy_grpc": {
|
||||
+ "cluster_name": "xds-grpc"
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ },
|
||||
+ "static_resources": {
|
||||
+ "clusters": [
|
||||
+ {
|
||||
+ "name": "prometheus_stats",
|
||||
+ "type": "STATIC",
|
||||
+ "connect_timeout": "0.250s",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "prometheus_stats",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "socket_address": {
|
||||
+ "protocol": "TCP",
|
||||
+ "address": "{{ .localhost }}",
|
||||
+ "port_value": {{ .config.ProxyAdminPort }}
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ "name": "agent",
|
||||
+ "type": "STATIC",
|
||||
+ "connect_timeout": "0.250s",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "agent",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "socket_address": {
|
||||
+ "protocol": "TCP",
|
||||
+ "address": "{{ .localhost }}",
|
||||
+ "port_value": {{ .config.StatusPort }}
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ "name": "sds-grpc",
|
||||
+ "type": "STATIC",
|
||||
+ "typed_extension_protocol_options": {
|
||||
+ "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
|
||||
+ "explicit_http_config": {
|
||||
+ "http2_protocol_options": {}
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ "connect_timeout": "1s",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "sds-grpc",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "pipe": {
|
||||
+ "path": "{{ .config.ConfigPath }}/SDS"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ "name": "xds-grpc",
|
||||
+ "type" : "STATIC",
|
||||
+ "connect_timeout": "1s",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "xds-grpc",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "pipe": {
|
||||
+ "path": "{{ .config.ConfigPath }}/XDS"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ },
|
||||
+ "circuit_breakers": {
|
||||
+ "thresholds": [
|
||||
+ {
|
||||
+ "priority": "DEFAULT",
|
||||
+ "max_connections": 100000,
|
||||
+ "max_pending_requests": 100000,
|
||||
+ "max_requests": 100000
|
||||
+ },
|
||||
+ {
|
||||
+ "priority": "HIGH",
|
||||
+ "max_connections": 100000,
|
||||
+ "max_pending_requests": 100000,
|
||||
+ "max_requests": 100000
|
||||
+ }
|
||||
+ ]
|
||||
+ },
|
||||
+ "upstream_connection_options": {
|
||||
+ "tcp_keepalive": {
|
||||
+ "keepalive_time": 300
|
||||
+ }
|
||||
+ },
|
||||
+ "max_requests_per_connection": 1,
|
||||
+ "typed_extension_protocol_options": {
|
||||
+ "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
|
||||
+ "explicit_http_config": {
|
||||
+ "http2_protocol_options": {}
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ {{ if .zipkin }}
|
||||
+ ,
|
||||
+ {
|
||||
+ "name": "zipkin",
|
||||
+ {{- if .tracing_tls }}
|
||||
+ "transport_socket": {{ .tracing_tls }},
|
||||
+ {{- end }}
|
||||
+ "type": "STRICT_DNS",
|
||||
+ "respect_dns_ttl": true,
|
||||
+ "dns_lookup_family": "{{ .dns_lookup_family }}",
|
||||
+ "dns_refresh_rate": "30s",
|
||||
+ "connect_timeout": "1s",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "zipkin",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "socket_address": {{ .zipkin }}
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ }
|
||||
+ }
|
||||
+ {{ else if .lightstep }}
|
||||
+ ,
|
||||
+ {
|
||||
+ "name": "lightstep",
|
||||
+ {{- if .tracing_tls }}
|
||||
+ "transport_socket": {{ .tracing_tls }},
|
||||
+ {{- end }}
|
||||
+ "typed_extension_protocol_options": {
|
||||
+ "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
|
||||
+ "explicit_http_config": {
|
||||
+ "http2_protocol_options": {}
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ "type": "STRICT_DNS",
|
||||
+ "respect_dns_ttl": true,
|
||||
+ "dns_lookup_family": "{{ .dns_lookup_family }}",
|
||||
+ "connect_timeout": "1s",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "lightstep",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "socket_address": {{ .lightstep }}
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ }
|
||||
+ }
|
||||
+ {{ else if .datadog }}
|
||||
+ ,
|
||||
+ {
|
||||
+ "name": "datadog_agent",
|
||||
+ {{- if .tracing_tls }}
|
||||
+ "transport_socket": {{ .tracing_tls }},
|
||||
+ {{- end }}
|
||||
+ "connect_timeout": "1s",
|
||||
+ "type": "STRICT_DNS",
|
||||
+ "respect_dns_ttl": true,
|
||||
+ "dns_lookup_family": "{{ .dns_lookup_family }}",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "datadog_agent",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "socket_address": {{ .datadog }}
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ }
|
||||
+ }
|
||||
+ {{ end }}
|
||||
+ {{- if .envoy_metrics_service_address }}
|
||||
+ ,
|
||||
+ {
|
||||
+ "name": "envoy_metrics_service",
|
||||
+ "type": "STRICT_DNS",
|
||||
+ {{- if .envoy_metrics_service_tls }}
|
||||
+ "transport_socket": {{ .envoy_metrics_service_tls }},
|
||||
+ {{- end }}
|
||||
+ {{- if .envoy_metrics_service_tcp_keepalive }}
|
||||
+ "upstream_connection_options": {{ .envoy_metrics_service_tcp_keepalive }},
|
||||
+ {{- end }}
|
||||
+ "respect_dns_ttl": true,
|
||||
+ "dns_lookup_family": "{{ .dns_lookup_family }}",
|
||||
+ "connect_timeout": "1s",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "typed_extension_protocol_options": {
|
||||
+ "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
|
||||
+ "explicit_http_config": {
|
||||
+ "http2_protocol_options": {}
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "envoy_metrics_service",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "socket_address": {{ .envoy_metrics_service_address }}
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ }
|
||||
+ }
|
||||
+ {{ end }}
|
||||
+ {{ if .envoy_accesslog_service_address }}
|
||||
+ ,
|
||||
+ {
|
||||
+ "name": "envoy_accesslog_service",
|
||||
+ "type": "STRICT_DNS",
|
||||
+ {{- if .envoy_accesslog_service_tls }}
|
||||
+ "transport_socket": {{ .envoy_accesslog_service_tls }},
|
||||
+ {{- end }}
|
||||
+ {{- if .envoy_accesslog_service_tcp_keepalive }}
|
||||
+ "upstream_connection_options": {{ .envoy_accesslog_service_tcp_keepalive }},
|
||||
+ {{ end }}
|
||||
+ "respect_dns_ttl": true,
|
||||
+ "dns_lookup_family": "{{ .dns_lookup_family }}",
|
||||
+ "connect_timeout": "1s",
|
||||
+ "lb_policy": "ROUND_ROBIN",
|
||||
+ "typed_extension_protocol_options": {
|
||||
+ "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
|
||||
+ "explicit_http_config": {
|
||||
+ "http2_protocol_options": {}
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ "load_assignment": {
|
||||
+ "cluster_name": "envoy_accesslog_service",
|
||||
+ "endpoints": [{
|
||||
+ "lb_endpoints": [{
|
||||
+ "endpoint": {
|
||||
+ "address":{
|
||||
+ "socket_address": {{ .envoy_accesslog_service_address }}
|
||||
+ }
|
||||
+ }
|
||||
+ }]
|
||||
+ }]
|
||||
+ }
|
||||
+ }
|
||||
+ {{ end }}
|
||||
+ ],
|
||||
+ "listeners":[
|
||||
+ {
|
||||
+ "address": {
|
||||
+ "socket_address": {
|
||||
+ "protocol": "TCP",
|
||||
+ "address": "{{ .wildcard }}",
|
||||
+ "port_value": {{ .envoy_prometheus_port }}
|
||||
+ }
|
||||
+ },
|
||||
+ "filter_chains": [
|
||||
+ {
|
||||
+ "filters": [
|
||||
+ {
|
||||
+ "name": "envoy.filters.network.http_connection_manager",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
+ "codec_type": "AUTO",
|
||||
+ "stat_prefix": "stats",
|
||||
+ "route_config": {
|
||||
+ "virtual_hosts": [
|
||||
+ {
|
||||
+ "name": "backend",
|
||||
+ "domains": [
|
||||
+ "*"
|
||||
+ ],
|
||||
+ "routes": [
|
||||
+ {
|
||||
+ "match": {
|
||||
+ "prefix": "/stats/prometheus"
|
||||
+ },
|
||||
+ "route": {
|
||||
+ "cluster": "prometheus_stats"
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ },
|
||||
+ "http_filters": [{
|
||||
+ "name": "envoy.filters.http.router",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
+ }
|
||||
+ }]
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ },
|
||||
+ {
|
||||
+ "address": {
|
||||
+ "socket_address": {
|
||||
+ "protocol": "TCP",
|
||||
+ "address": "{{ .wildcard }}",
|
||||
+ "port_value": {{ .envoy_status_port }}
|
||||
+ }
|
||||
+ },
|
||||
+ "filter_chains": [
|
||||
+ {
|
||||
+ "filters": [
|
||||
+ {
|
||||
+ "name": "envoy.filters.network.http_connection_manager",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
|
||||
+ "codec_type": "AUTO",
|
||||
+ "stat_prefix": "agent",
|
||||
+ "route_config": {
|
||||
+ "virtual_hosts": [
|
||||
+ {
|
||||
+ "name": "backend",
|
||||
+ "domains": [
|
||||
+ "*"
|
||||
+ ],
|
||||
+ "routes": [
|
||||
+ {
|
||||
+ "match": {
|
||||
+ "prefix": "/healthz/ready"
|
||||
+ },
|
||||
+ "route": {
|
||||
+ "cluster": "agent"
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ },
|
||||
+ "http_filters": [{
|
||||
+ "name": "envoy.filters.http.router",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
|
||||
+ }
|
||||
+ }]
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ {{- if .zipkin }}
|
||||
+ ,
|
||||
+ "tracing": {
|
||||
+ "http": {
|
||||
+ "name": "envoy.tracers.zipkin",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
|
||||
+ "collector_cluster": "zipkin",
|
||||
+ "collector_endpoint": "/api/v2/spans",
|
||||
+ "collector_endpoint_version": "HTTP_JSON",
|
||||
+ "trace_id_128bit": true,
|
||||
+ "shared_span_context": false
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ {{- else if .lightstep }}
|
||||
+ ,
|
||||
+ "tracing": {
|
||||
+ "http": {
|
||||
+ "name": "envoy.tracers.lightstep",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.config.trace.v3.LightstepConfig",
|
||||
+ "collector_cluster": "lightstep",
|
||||
+ "access_token_file": "{{ .lightstepToken}}"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ {{- else if .datadog }}
|
||||
+ ,
|
||||
+ "tracing": {
|
||||
+ "http": {
|
||||
+ "name": "envoy.tracers.datadog",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.config.trace.v3.DatadogConfig",
|
||||
+ "collector_cluster": "datadog_agent",
|
||||
+ "service_name": "{{ .cluster }}"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ {{- else if .openCensusAgent }}
|
||||
+ ,
|
||||
+ "tracing": {
|
||||
+ "http": {
|
||||
+ "name": "envoy.tracers.opencensus",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig",
|
||||
+ "ocagent_exporter_enabled": true,
|
||||
+ "ocagent_address": "{{ .openCensusAgent }}",
|
||||
+ "incoming_trace_context": {{ .openCensusAgentContexts }},
|
||||
+ "outgoing_trace_context": {{ .openCensusAgentContexts }},
|
||||
+ "trace_config": {
|
||||
+ "constant_sampler": {
|
||||
+ "decision": "ALWAYS_PARENT"
|
||||
+ },
|
||||
+ "max_number_of_annotations": 200,
|
||||
+ "max_number_of_attributes": 200,
|
||||
+ "max_number_of_message_events": 200,
|
||||
+ "max_number_of_links": 200
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ {{- else if .stackdriver }}
|
||||
+ ,
|
||||
+ "tracing": {
|
||||
+ "http": {
|
||||
+ "name": "envoy.tracers.opencensus",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig",
|
||||
+ "stackdriver_exporter_enabled": true,
|
||||
+ "stackdriver_project_id": "{{ .stackdriverProjectID }}",
|
||||
+ {{ if .sts_port }}
|
||||
+ "stackdriver_grpc_service": {
|
||||
+ "google_grpc": {
|
||||
+ "target_uri": "cloudtrace.googleapis.com",
|
||||
+ "stat_prefix": "oc_stackdriver_tracer",
|
||||
+ "channel_credentials": {
|
||||
+ "ssl_credentials": {}
|
||||
+ },
|
||||
+ "call_credentials": [{
|
||||
+ "sts_service": {
|
||||
+ "token_exchange_service_uri": "http://localhost:{{ .sts_port }}/token",
|
||||
+ "subject_token_path": "/var/run/secrets/tokens/istio-token",
|
||||
+ "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
|
||||
+ "scope": "https://www.googleapis.com/auth/cloud-platform"
|
||||
+ }
|
||||
+ }]
|
||||
+ },
|
||||
+ "initial_metadata": [
|
||||
+ {{ if .gcp_project_id }}
|
||||
+ {
|
||||
+ "key": "x-goog-user-project",
|
||||
+ "value": "{{ .gcp_project_id }}"
|
||||
+ }
|
||||
+ {{ end }}
|
||||
+ ]
|
||||
+ },
|
||||
+ {{ end }}
|
||||
+ "stdout_exporter_enabled": {{ .stackdriverDebug }},
|
||||
+ "incoming_trace_context": ["CLOUD_TRACE_CONTEXT", "TRACE_CONTEXT", "GRPC_TRACE_BIN", "B3"],
|
||||
+ "outgoing_trace_context": ["CLOUD_TRACE_CONTEXT", "TRACE_CONTEXT", "GRPC_TRACE_BIN", "B3"],
|
||||
+ "trace_config":{
|
||||
+ "constant_sampler":{
|
||||
+ "decision": "ALWAYS_PARENT"
|
||||
+ },
|
||||
+ "max_number_of_annotations": {{ .stackdriverMaxAnnotations }},
|
||||
+ "max_number_of_attributes": {{ .stackdriverMaxAttributes }},
|
||||
+ "max_number_of_message_events": {{ .stackdriverMaxEvents }},
|
||||
+ "max_number_of_links": 200
|
||||
+ }
|
||||
+ }
|
||||
+ }}
|
||||
+ {{ end }}
|
||||
+ {{ if or .envoy_metrics_service_address .statsd }}
|
||||
+ ,
|
||||
+ "stats_sinks": [
|
||||
+ {{ if .envoy_metrics_service_address }}
|
||||
+ {
|
||||
+ "name": "envoy.stat_sinks.metrics_service",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.config.metrics.v3.MetricsServiceConfig",
|
||||
+ "transport_api_version": "V3",
|
||||
+ "grpc_service": {
|
||||
+ "envoy_grpc": {
|
||||
+ "cluster_name": "envoy_metrics_service"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ {{ end }}
|
||||
+ {{ if and .envoy_metrics_service_address .statsd }}
|
||||
+ ,
|
||||
+ {{ end }}
|
||||
+ {{ if .statsd }}
|
||||
+ {
|
||||
+ "name": "envoy.stat_sinks.statsd",
|
||||
+ "typed_config": {
|
||||
+ "@type": "type.googleapis.com/envoy.config.metrics.v3.StatsdSink",
|
||||
+ "address": {
|
||||
+ "socket_address": {{ .statsd }}
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ {{ end }}
|
||||
+ ]
|
||||
+ {{ end }}
|
||||
+ {{ if .outlier_log_path }}
|
||||
+ ,
|
||||
+ "cluster_manager": {
|
||||
+ "outlier_detection": {
|
||||
+ "event_log_path": "{{ .outlier_log_path }}"
|
||||
+ }
|
||||
+ }
|
||||
+ {{ end }}
|
||||
+}
|
||||
diff -Naur istio/tools/packaging/common/higress-proxy-container-init.sh istio-new/tools/packaging/common/higress-proxy-container-init.sh
|
||||
--- istio/tools/packaging/common/higress-proxy-container-init.sh 1970-01-01 08:00:00.000000000 +0800
|
||||
+++ istio-new/tools/packaging/common/higress-proxy-container-init.sh 2024-05-19 16:30:06.202757394 +0800
|
||||
@@ -0,0 +1,32 @@
|
||||
+#!/bin/bash
|
||||
+
|
||||
+mkdir -p /var/log/proxy
|
||||
+
|
||||
+mkdir -p /var/lib/istio
|
||||
+
|
||||
+chown -R 1337.1337 /var/log/proxy
|
||||
+
|
||||
+chown -R 1337.1337 /var/lib/logrotate
|
||||
+
|
||||
+chown -R 1337.1337 /var/lib/istio
|
||||
+
|
||||
+cat <<EOF > /etc/logrotate.d/higress-logrotate
|
||||
+/var/log/proxy/access.log
|
||||
+{
|
||||
+su 1337 1337
|
||||
+rotate 5
|
||||
+create 644 1337 1337
|
||||
+nocompress
|
||||
+notifempty
|
||||
+minsize 100M
|
||||
+postrotate
|
||||
+ ps aux|grep "envoy -c"|grep -v "grep"|awk '{print $2}'|xargs -i kill -SIGUSR1 {}
|
||||
+endscript
|
||||
+}
|
||||
+EOF
|
||||
+
|
||||
+chmod -R 0644 /etc/logrotate.d/higress-logrotate
|
||||
+
|
||||
+cat <<EOF > /var/lib/istio/cron.txt
|
||||
+* * * * * /usr/sbin/logrotate /etc/logrotate.d/higress-logrotate
|
||||
+EOF
|
||||
diff -Naur istio/tools/packaging/common/higress-proxy-start.sh istio-new/tools/packaging/common/higress-proxy-start.sh
|
||||
--- istio/tools/packaging/common/higress-proxy-start.sh 1970-01-01 08:00:00.000000000 +0800
|
||||
+++ istio-new/tools/packaging/common/higress-proxy-start.sh 2024-05-19 16:33:18.802761176 +0800
|
||||
@@ -0,0 +1,10 @@
|
||||
+#!/bin/bash
|
||||
+
|
||||
+if [ -n "$LITE_METRICS" ]; then
|
||||
+ cp /var/lib/istio/envoy/envoy_bootstrap_lite_tmpl.json /var/lib/istio/envoy/envoy_bootstrap_tmpl.json
|
||||
+fi
|
||||
+
|
||||
+nohup supercronic /var/lib/istio/cron.txt &> /dev/null &
|
||||
+
|
||||
+/usr/local/bin/pilot-agent $*
|
||||
+
|
||||
83
istio/1.12/patches/istio/20240521-optimize-bootstrap.patch
Normal file
83
istio/1.12/patches/istio/20240521-optimize-bootstrap.patch
Normal file
@@ -0,0 +1,83 @@
|
||||
diff -Naur istio/tools/packaging/common/envoy_bootstrap.json istio-new/tools/packaging/common/envoy_bootstrap.json
|
||||
--- istio/tools/packaging/common/envoy_bootstrap.json 2024-05-21 23:46:21.000000000 +0800
|
||||
+++ istio-new/tools/packaging/common/envoy_bootstrap.json 2024-05-21 23:47:54.000000000 +0800
|
||||
@@ -37,55 +37,15 @@
|
||||
"use_all_default_tags": false,
|
||||
"stats_tags": [
|
||||
{
|
||||
- "tag_name": "phase",
|
||||
- "regex": "(_phase=([a-z_]+))"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "ruleid",
|
||||
- "regex": "(_ruleid=([0-9]+))"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "route",
|
||||
- "regex": "^vhost\\..*?\\.route\\.([^\\.]+\\.)upstream"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "ecds_name",
|
||||
- "regex": "extension_config_discovery\\.(.*?\\.)[^\\.]+$"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "rds_name",
|
||||
- "regex": "rds\\.(.*?\\.)[^\\.]+$"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "sds_name",
|
||||
- "regex": "sds\\.(.*?\\.)[^\\.]+$"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "vhost",
|
||||
- "regex": "^vhost\\.((.*?)\\.)(vcluster|route)"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "vcluster",
|
||||
- "regex": "vcluster\\.((.*?)\\.)upstream"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "dest_zone",
|
||||
- "regex": "zone\\.[^\\.]+\\.([^\\.]+\\.)"
|
||||
- },
|
||||
- {
|
||||
- "tag_name": "from_zone",
|
||||
- "regex": "zone\\.([^\\.]+\\.)"
|
||||
- },
|
||||
- {
|
||||
"tag_name": "cluster_name",
|
||||
- "regex": "^cluster\\.((.*?)\\.)(http1\\.|http2\\.|health_check\\.|zone\\.|external\\.|circuit_breakers\\.|[^\\.]+$)"
|
||||
+ "regex": "^cluster\\.((.+?(\\..+?\\.svc\\.cluster\\.local)?)\\.)"
|
||||
},
|
||||
{
|
||||
"tag_name": "tcp_prefix",
|
||||
"regex": "^tcp\\.((.*?)\\.)\\w+?$"
|
||||
},
|
||||
{
|
||||
- "regex": "(response_code=\\.=(.+?);\\.;)|_rq(_(\\.d{3}))$",
|
||||
+ "regex": "_rq(_(\\d{3}))$",
|
||||
"tag_name": "response_code"
|
||||
},
|
||||
{
|
||||
@@ -98,7 +58,7 @@
|
||||
},
|
||||
{
|
||||
"tag_name": "http_conn_manager_prefix",
|
||||
- "regex": "^http\\.(((outbound_([0-9]{1,3}\\.{0,1}){4}_\\d{0,5})|([^\\.]+))\\.)"
|
||||
+ "regex": "^http\\.(((?:[_.[:digit:]]*|[_\\[\\]aAbBcCdDeEfF[:digit:]]*))\\.)"
|
||||
},
|
||||
{
|
||||
"tag_name": "listener_address",
|
||||
@@ -108,12 +68,6 @@
|
||||
"tag_name": "mongo_prefix",
|
||||
"regex": "^mongo\\.(.+?)\\.(collection|cmd|cx_|op_|delays_|decoding_)(.*?)$"
|
||||
},
|
||||
- {{- range $a, $tag := .extraStatTags }}
|
||||
- {
|
||||
- "regex": "({{ $tag }}=\\.=(.*?);\\.;)",
|
||||
- "tag_name": "{{ $tag }}"
|
||||
- },
|
||||
- {{- end }}
|
||||
{
|
||||
"regex": "(cache\\.(.+?)\\.)",
|
||||
"tag_name": "cache"
|
||||
38
istio/1.12/patches/proxy/20240519-v8-upgrade.patch
Normal file
38
istio/1.12/patches/proxy/20240519-v8-upgrade.patch
Normal file
@@ -0,0 +1,38 @@
|
||||
diff -Naur proxy/scripts/release-binary.sh proxy-new/scripts/release-binary.sh
|
||||
--- proxy/scripts/release-binary.sh 2024-05-19 12:33:33.254478650 +0800
|
||||
+++ proxy-new/scripts/release-binary.sh 2024-05-19 12:31:11.714475870 +0800
|
||||
@@ -112,7 +112,7 @@
|
||||
# k8-opt is the output directory for x86_64 optimized builds (-c opt, so --config=release-symbol and --config=release).
|
||||
# k8-dbg is the output directory for -c dbg builds.
|
||||
#for config in release release-symbol debug
|
||||
-for config in release
|
||||
+for config in release release-symbol
|
||||
do
|
||||
case $config in
|
||||
"release" )
|
||||
diff -Naur proxy/scripts/release-binary.sh proxy-new/scripts/release-binary.sh
|
||||
--- proxy/scripts/release-binary.sh 2024-05-19 12:27:51.030471929 +0800
|
||||
+++ proxy-new/scripts/release-binary.sh 2024-05-19 12:04:55.738444918 +0800
|
||||
@@ -152,10 +152,6 @@
|
||||
echo "Building ${config} proxy"
|
||||
BINARY_NAME="${HOME}/package/${BINARY_BASE_NAME}.tar.gz"
|
||||
SHA256_NAME="${HOME}/${BINARY_BASE_NAME}-${SHA}.sha256"
|
||||
- # All cores are used by com_googlesource_chromium_v8:build within.
|
||||
- # Prebuild this target to avoid stacking this ram intensive task with others.
|
||||
- # shellcheck disable=SC2086
|
||||
- bazel build ${BAZEL_BUILD_ARGS} ${CONFIG_PARAMS} @com_googlesource_chromium_v8//:build
|
||||
# shellcheck disable=SC2086
|
||||
bazel build ${BAZEL_BUILD_ARGS} ${CONFIG_PARAMS} //src/envoy:envoy_tar
|
||||
BAZEL_TARGET="${BAZEL_OUT}/src/envoy/envoy_tar.tar.gz"
|
||||
diff -Naur proxy/tools/deb/test/build_docker.sh proxy-new/tools/deb/test/build_docker.sh
|
||||
--- proxy/tools/deb/test/build_docker.sh 2024-05-19 12:27:51.030471929 +0800
|
||||
+++ proxy-new/tools/deb/test/build_docker.sh 2024-05-19 12:05:07.978445159 +0800
|
||||
@@ -20,8 +20,6 @@
|
||||
# Script requires a working docker on the test machine
|
||||
# It is run in the proxy dir, will create a docker image with proxy deb installed
|
||||
|
||||
-
|
||||
-bazel build @com_googlesource_chromium_v8//:build
|
||||
bazel build tools/deb:istio-proxy
|
||||
|
||||
PROJECT="istio-testing"
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cert"
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/common"
|
||||
"github.com/alibaba/higress/pkg/ingress/mcp"
|
||||
"github.com/alibaba/higress/pkg/ingress/translation"
|
||||
@@ -112,6 +113,9 @@ type ServerArgs struct {
|
||||
GatewaySelectorValue string
|
||||
GatewayHttpPort uint32
|
||||
GatewayHttpsPort uint32
|
||||
EnableAutomaticHttps bool
|
||||
AutomaticHttpsEmail string
|
||||
CertHttpAddress string
|
||||
}
|
||||
|
||||
type readinessProbe func() (bool, error)
|
||||
@@ -133,6 +137,7 @@ type Server struct {
|
||||
xdsServer *xds.DiscoveryServer
|
||||
server server.Instance
|
||||
readinessProbes map[string]readinessProbe
|
||||
certServer *cert.Server
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -168,6 +173,7 @@ func NewServer(args *ServerArgs) (*Server, error) {
|
||||
s.initConfigController,
|
||||
s.initRegistryEventHandlers,
|
||||
s.initAuthenticators,
|
||||
s.initAutomaticHttps,
|
||||
}
|
||||
|
||||
for _, f := range initFuncList {
|
||||
@@ -287,6 +293,15 @@ func (s *Server) Start(stop <-chan struct{}) error {
|
||||
}
|
||||
}()
|
||||
|
||||
if s.EnableAutomaticHttps {
|
||||
go func() {
|
||||
log.Infof("starting Automatic Cert HTTP service at %s", s.CertHttpAddress)
|
||||
if err := s.certServer.Run(stop); err != nil {
|
||||
log.Errorf("error serving Automatic Cert HTTP server: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
s.waitForShutDown(stop)
|
||||
return nil
|
||||
}
|
||||
@@ -370,6 +385,26 @@ func (s *Server) initAuthenticators() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) initAutomaticHttps() error {
|
||||
certOption := &cert.Option{
|
||||
Namespace: PodNamespace,
|
||||
ServerAddress: s.CertHttpAddress,
|
||||
Email: s.AutomaticHttpsEmail,
|
||||
}
|
||||
certServer, err := cert.NewServer(s.kubeClient.Kube(), certOption)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.certServer = certServer
|
||||
log.Infof("init cert default config")
|
||||
s.certServer.InitDefaultConfig()
|
||||
if !s.EnableAutomaticHttps {
|
||||
log.Info("automatic https is disabled")
|
||||
return nil
|
||||
}
|
||||
return s.certServer.InitServer()
|
||||
}
|
||||
|
||||
func (s *Server) initKubeClient() error {
|
||||
if s.kubeClient != nil {
|
||||
// Already initialized by startup arguments
|
||||
@@ -398,6 +433,7 @@ func (s *Server) initHttpServer() error {
|
||||
}
|
||||
s.xdsServer.AddDebugHandlers(s.httpMux, nil, true, nil)
|
||||
s.httpMux.HandleFunc("/ready", s.readyHandler)
|
||||
s.httpMux.HandleFunc("/registry/watcherStatus", s.registryWatcherStatusHandler)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -413,6 +449,43 @@ func (s *Server) readyHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (s *Server) registryWatcherStatusHandler(w http.ResponseWriter, _ *http.Request) {
|
||||
ingressTranslation, ok := s.environment.IngressStore.(*translation.IngressTranslation)
|
||||
if !ok {
|
||||
http.Error(w, "IngressStore not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
ingressConfig := ingressTranslation.GetIngressConfig()
|
||||
if ingressConfig == nil {
|
||||
http.Error(w, "IngressConfig not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
registryReconciler := ingressConfig.RegistryReconciler
|
||||
if registryReconciler == nil {
|
||||
http.Error(w, "RegistryReconciler not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
watcherStatusList := registryReconciler.GetRegistryWatcherStatusList()
|
||||
writeJSON(w, watcherStatusList)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, obj interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
b, err := config.ToJSON(obj)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// cachesSynced checks whether caches have been synced.
|
||||
func (s *Server) cachesSynced() bool {
|
||||
return s.configController.HasSynced()
|
||||
|
||||
219
pkg/cert/certmgr.go
Normal file
219
pkg/cert/certmgr.go
Normal file
@@ -0,0 +1,219 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
EventCertObtained = "cert_obtained"
|
||||
)
|
||||
|
||||
type CertMgr struct {
|
||||
cfg *certmagic.Config
|
||||
client kubernetes.Interface
|
||||
namespace string
|
||||
mux sync.RWMutex
|
||||
storage certmagic.Storage
|
||||
cache *certmagic.Cache
|
||||
myACME *certmagic.ACMEIssuer
|
||||
ingressSolver acmez.Solver
|
||||
configMgr *ConfigMgr
|
||||
secretMgr *SecretMgr
|
||||
}
|
||||
|
||||
func InitCertMgr(opts *Option, clientSet kubernetes.Interface, config *Config) (*CertMgr, error) {
|
||||
CertLog.Infof("certmgr init config: %+v", config)
|
||||
// Init certmagic config
|
||||
// First make a pointer to a Cache as we need to reference the same Cache in
|
||||
// GetConfigForCert below.
|
||||
var cache *certmagic.Cache
|
||||
var storage certmagic.Storage
|
||||
storage, _ = NewConfigmapStorage(opts.Namespace, clientSet)
|
||||
renewalWindowRatio := float64(config.RenewBeforeDays / RenewMaxDays)
|
||||
magicConfig := certmagic.Config{
|
||||
RenewalWindowRatio: renewalWindowRatio,
|
||||
Storage: storage,
|
||||
}
|
||||
cache = certmagic.NewCache(certmagic.CacheOptions{
|
||||
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
||||
// Here we use New to get a valid Config associated with the same cache.
|
||||
// The provided Config is used as a template and will be completed with
|
||||
// any defaults that are set in the Default config.
|
||||
return certmagic.New(cache, magicConfig), nil
|
||||
},
|
||||
})
|
||||
// init certmagic
|
||||
cfg := certmagic.New(cache, magicConfig)
|
||||
// Init certmagic acme
|
||||
issuer := config.GetIssuer(IssuerTypeLetsencrypt)
|
||||
if issuer == nil {
|
||||
// should never happen here
|
||||
return nil, fmt.Errorf("there is no Letsencrypt Issuer found in config")
|
||||
}
|
||||
|
||||
myACME := certmagic.NewACMEIssuer(cfg, certmagic.ACMEIssuer{
|
||||
//CA: certmagic.LetsEncryptStagingCA,
|
||||
CA: certmagic.LetsEncryptProductionCA,
|
||||
Email: issuer.Email,
|
||||
Agreed: true,
|
||||
DisableHTTPChallenge: false,
|
||||
DisableTLSALPNChallenge: true,
|
||||
})
|
||||
// inject http01 solver
|
||||
ingressSolver, _ := NewIngressSolver(opts.Namespace, clientSet, myACME)
|
||||
myACME.Http01Solver = ingressSolver
|
||||
// init issuers
|
||||
cfg.Issuers = []certmagic.Issuer{myACME}
|
||||
|
||||
configMgr, _ := NewConfigMgr(opts.Namespace, clientSet)
|
||||
secretMgr, _ := NewSecretMgr(opts.Namespace, clientSet)
|
||||
|
||||
certMgr := &CertMgr{
|
||||
cfg: cfg,
|
||||
client: clientSet,
|
||||
namespace: opts.Namespace,
|
||||
myACME: myACME,
|
||||
ingressSolver: ingressSolver,
|
||||
configMgr: configMgr,
|
||||
secretMgr: secretMgr,
|
||||
cache: cache,
|
||||
}
|
||||
certMgr.cfg.OnEvent = certMgr.OnEvent
|
||||
return certMgr, nil
|
||||
}
|
||||
func (s *CertMgr) Reconcile(ctx context.Context, oldConfig *Config, newConfig *Config) error {
|
||||
CertLog.Infof("cermgr reconcile old config:%+v to new config:%+v", oldConfig, newConfig)
|
||||
// sync email
|
||||
if oldConfig != nil && newConfig != nil {
|
||||
oldIssuer := oldConfig.GetIssuer(IssuerTypeLetsencrypt)
|
||||
newIssuer := newConfig.GetIssuer(IssuerTypeLetsencrypt)
|
||||
if oldIssuer.Email != newIssuer.Email {
|
||||
// TODO before sync email, maybe need to clean up cache and account
|
||||
}
|
||||
}
|
||||
|
||||
// sync domains
|
||||
newDomains := make([]string, 0)
|
||||
newDomainsMap := make(map[string]string, 0)
|
||||
removeDomains := make([]string, 0)
|
||||
|
||||
if newConfig != nil {
|
||||
for _, config := range newConfig.CredentialConfig {
|
||||
if config.TLSIssuer == IssuerTypeLetsencrypt {
|
||||
for _, newDomain := range config.Domains {
|
||||
newDomains = append(newDomains, newDomain)
|
||||
newDomainsMap[newDomain] = newDomain
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if oldConfig != nil {
|
||||
for _, config := range oldConfig.CredentialConfig {
|
||||
if config.TLSIssuer == IssuerTypeLetsencrypt {
|
||||
for _, oldDomain := range config.Domains {
|
||||
if _, ok := newDomainsMap[oldDomain]; !ok {
|
||||
removeDomains = append(removeDomains, oldDomain)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if newConfig.AutomaticHttps == true {
|
||||
newIssuer := newConfig.GetIssuer(IssuerTypeLetsencrypt)
|
||||
// clean up unused domains
|
||||
s.cleanSync(context.Background(), removeDomains)
|
||||
// sync email
|
||||
s.myACME.Email = newIssuer.Email
|
||||
// sync RenewalWindowRatio
|
||||
s.cfg.RenewalWindowRatio = float64(newConfig.RenewBeforeDays / RenewMaxDays)
|
||||
// start cache
|
||||
s.cache.Start()
|
||||
// sync domains
|
||||
s.manageSync(context.Background(), newDomains)
|
||||
s.configMgr.SetConfig(newConfig)
|
||||
} else {
|
||||
// stop cache maintainAssets
|
||||
s.cache.Stop()
|
||||
s.configMgr.SetConfig(newConfig)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CertMgr) manageSync(ctx context.Context, domainNames []string) error {
|
||||
CertLog.Infof("cert manage sync domains:%v", domainNames)
|
||||
return s.cfg.ManageSync(ctx, domainNames)
|
||||
}
|
||||
|
||||
func (s *CertMgr) cleanSync(ctx context.Context, domainNames []string) error {
|
||||
//TODO implement clean up domains
|
||||
CertLog.Infof("cert clean sync domains:%v", domainNames)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CertMgr) OnEvent(ctx context.Context, event string, data map[string]any) error {
|
||||
CertLog.Infof("certmgr receive event:% data:%+v", event, data)
|
||||
/**
|
||||
event: cert_obtained
|
||||
cfg.emit(ctx, "cert_obtained", map[string]any{
|
||||
"renewal": true,
|
||||
"remaining": timeLeft,
|
||||
"identifier": name,
|
||||
"issuer": issuerKey,
|
||||
"storage_path": StorageKeys.CertsSitePrefix(issuerKey, certKey),
|
||||
"private_key_path": StorageKeys.SitePrivateKey(issuerKey, certKey),
|
||||
"certificate_path": StorageKeys.SiteCert(issuerKey, certKey),
|
||||
"metadata_path": StorageKeys.SiteMeta(issuerKey, certKey),
|
||||
})
|
||||
*/
|
||||
if event == EventCertObtained {
|
||||
// obtain certificate and update secret
|
||||
domain := data["identifier"].(string)
|
||||
isRenew := data["renewal"].(bool)
|
||||
privateKeyPath := data["private_key_path"].(string)
|
||||
certificatePath := data["certificate_path"].(string)
|
||||
privateKey, err := s.cfg.Storage.Load(context.Background(), privateKeyPath)
|
||||
certificate, err := s.cfg.Storage.Load(context.Background(), certificatePath)
|
||||
certChain, err := parseCertsFromPEMBundle(certificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
notAfterTime := notAfter(certChain[0])
|
||||
notBeforeTime := notBefore(certChain[0])
|
||||
secretName := s.configMgr.GetConfig().GetSecretNameByDomain(IssuerTypeLetsencrypt, domain)
|
||||
if len(secretName) == 0 {
|
||||
CertLog.Errorf("can not find secret name for domain % in config", domain)
|
||||
return nil
|
||||
}
|
||||
err2 := s.secretMgr.Update(domain, secretName, privateKey, certificate, notBeforeTime, notAfterTime, isRenew)
|
||||
if err2 != nil {
|
||||
CertLog.Errorf("update secretName %s for domain %s error: %v", secretName, domain, err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
290
pkg/cert/config.go
Normal file
290
pkg/cert/config.go
Normal file
@@ -0,0 +1,290 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"istio.io/istio/pkg/config/host"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigmapCertName = "higress-https"
|
||||
ConfigmapCertConfigKey = "cert"
|
||||
DefaultRenewBeforeDays = 30
|
||||
RenewMaxDays = 90
|
||||
)
|
||||
|
||||
type IssuerName string
|
||||
|
||||
const (
|
||||
IssuerTypeAliyunSSL IssuerName = "aliyunssl"
|
||||
IssuerTypeLetsencrypt IssuerName = "letsencrypt"
|
||||
)
|
||||
|
||||
// Config is the configuration of automatic https.
|
||||
type Config struct {
|
||||
AutomaticHttps bool `json:"automaticHttps"`
|
||||
RenewBeforeDays int `json:"renewBeforeDays"`
|
||||
CredentialConfig []CredentialEntry `json:"credentialConfig"`
|
||||
ACMEIssuer []ACMEIssuerEntry `json:"acmeIssuer"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
func (c *Config) GetIssuer(issuerName IssuerName) *ACMEIssuerEntry {
|
||||
for _, issuer := range c.ACMEIssuer {
|
||||
if issuer.Name == issuerName {
|
||||
return &issuer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) MatchSecretNameByDomain(domain string) string {
|
||||
for _, credential := range c.CredentialConfig {
|
||||
for _, credDomain := range credential.Domains {
|
||||
if host.Name(strings.ToLower(domain)).SubsetOf(host.Name(strings.ToLower(credDomain))) {
|
||||
return credential.TLSSecret
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Config) GetSecretNameByDomain(issuerName IssuerName, domain string) string {
|
||||
for _, credential := range c.CredentialConfig {
|
||||
if credential.TLSIssuer == issuerName {
|
||||
for _, credDomain := range credential.Domains {
|
||||
if host.Name(strings.ToLower(domain)).SubsetOf(host.Name(strings.ToLower(credDomain))) {
|
||||
return credential.TLSSecret
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
// check acmeIssuer
|
||||
if len(c.ACMEIssuer) == 0 {
|
||||
return fmt.Errorf("acmeIssuer is empty")
|
||||
}
|
||||
for _, issuer := range c.ACMEIssuer {
|
||||
switch issuer.Name {
|
||||
case IssuerTypeLetsencrypt:
|
||||
if issuer.Email == "" {
|
||||
return fmt.Errorf("acmeIssuer %s email is empty", issuer.Name)
|
||||
}
|
||||
if !ValidateEmail(issuer.Email) {
|
||||
return fmt.Errorf("acmeIssuer %s email %s is invalid", issuer.Name, issuer.Email)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("acmeIssuer name %s is not supported", issuer.Name)
|
||||
}
|
||||
}
|
||||
// check credentialConfig
|
||||
for _, credential := range c.CredentialConfig {
|
||||
if len(credential.Domains) == 0 {
|
||||
return fmt.Errorf("credentialConfig domains is empty")
|
||||
}
|
||||
if credential.TLSSecret == "" {
|
||||
return fmt.Errorf("credentialConfig tlsSecret is empty")
|
||||
}
|
||||
if credential.TLSIssuer == IssuerTypeLetsencrypt {
|
||||
if len(credential.Domains) > 1 {
|
||||
return fmt.Errorf("credentialConfig tlsIssuer %s only support one domain", credential.TLSIssuer)
|
||||
}
|
||||
}
|
||||
if credential.TLSIssuer != IssuerTypeLetsencrypt && len(credential.TLSIssuer) > 0 {
|
||||
return fmt.Errorf("credential tls issuer %s is not support", credential.TLSIssuer)
|
||||
}
|
||||
}
|
||||
|
||||
if c.RenewBeforeDays <= 0 {
|
||||
return fmt.Errorf("RenewBeforeDays should be large than zero")
|
||||
}
|
||||
|
||||
if c.RenewBeforeDays >= RenewMaxDays {
|
||||
return fmt.Errorf("RenewBeforeDays should be less than %d", RenewMaxDays)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CredentialEntry struct {
|
||||
Domains []string `json:"domains"`
|
||||
TLSIssuer IssuerName `json:"tlsIssuer,omitempty"`
|
||||
TLSSecret string `json:"tlsSecret,omitempty"`
|
||||
CACertSecret string `json:"cacertSecret,omitempty"`
|
||||
}
|
||||
|
||||
type ACMEIssuerEntry struct {
|
||||
Name IssuerName `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AK string `json:"ak"` // Only applicable for certain issuers like 'aliyunssl'
|
||||
SK string `json:"sk"` // Only applicable for certain issuers like 'aliyunssl'
|
||||
}
|
||||
type ConfigMgr struct {
|
||||
client kubernetes.Interface
|
||||
config atomic.Value
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (c *ConfigMgr) SetConfig(config *Config) {
|
||||
c.config.Store(config)
|
||||
}
|
||||
|
||||
func (c *ConfigMgr) GetConfig() *Config {
|
||||
value := c.config.Load()
|
||||
if value != nil {
|
||||
if config, ok := value.(*Config); ok {
|
||||
return config
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ConfigMgr) InitConfig(email string) (*Config, error) {
|
||||
var defaultConfig *Config
|
||||
cm, err := c.GetConfigmap()
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
if len(strings.TrimSpace(email)) == 0 {
|
||||
email = getRandEmail()
|
||||
}
|
||||
defaultConfig = newDefaultConfig(email)
|
||||
err2 := c.ApplyConfigmap(defaultConfig)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
} else {
|
||||
defaultConfig, err = c.ParseConfigFromConfigmap(cm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return defaultConfig, nil
|
||||
}
|
||||
|
||||
func (c *ConfigMgr) ParseConfigFromConfigmap(configmap *v1.ConfigMap) (*Config, error) {
|
||||
if _, ok := configmap.Data[ConfigmapCertConfigKey]; !ok {
|
||||
return nil, fmt.Errorf("no cert key %s in configmap %s", ConfigmapCertConfigKey, configmap.Name)
|
||||
}
|
||||
|
||||
config := newDefaultConfig("")
|
||||
if err := yaml.Unmarshal([]byte(configmap.Data[ConfigmapCertConfigKey]), config); err != nil {
|
||||
return nil, fmt.Errorf("data:%s, convert to higress config error, error: %+v", configmap.Data[ConfigmapCertConfigKey], err)
|
||||
}
|
||||
// validate config
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *ConfigMgr) GetConfigFromConfigmap() (*Config, error) {
|
||||
var config *Config
|
||||
cm, err := c.GetConfigmap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
config, err = c.ParseConfigFromConfigmap(cm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *ConfigMgr) GetConfigmap() (configmap *v1.ConfigMap, err error) {
|
||||
configmapName := ConfigmapCertName
|
||||
cm, err := c.client.CoreV1().ConfigMaps(c.namespace).Get(context.Background(), configmapName, metav1.GetOptions{})
|
||||
return cm, err
|
||||
}
|
||||
|
||||
func (c *ConfigMgr) ApplyConfigmap(config *Config) error {
|
||||
configmapName := ConfigmapCertName
|
||||
cm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: c.namespace,
|
||||
Name: configmapName,
|
||||
},
|
||||
}
|
||||
bytes, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.Data = make(map[string]string, 0)
|
||||
cm.Data[ConfigmapCertConfigKey] = string(bytes)
|
||||
|
||||
_, err = c.client.CoreV1().ConfigMaps(c.namespace).Get(context.Background(), configmapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
if _, err = c.client.CoreV1().ConfigMaps(c.namespace).Create(context.Background(), cm, metav1.CreateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err = c.client.CoreV1().ConfigMaps(c.namespace).Update(context.Background(), cm, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewConfigMgr(namespace string, client kubernetes.Interface) (*ConfigMgr, error) {
|
||||
configMgr := &ConfigMgr{
|
||||
client: client,
|
||||
namespace: namespace,
|
||||
}
|
||||
return configMgr, nil
|
||||
}
|
||||
|
||||
func newDefaultConfig(email string) *Config {
|
||||
|
||||
defaultIssuer := []ACMEIssuerEntry{
|
||||
{
|
||||
Name: IssuerTypeLetsencrypt,
|
||||
Email: email,
|
||||
},
|
||||
}
|
||||
defaultCredentialConfig := make([]CredentialEntry, 0)
|
||||
config := &Config{
|
||||
AutomaticHttps: true,
|
||||
RenewBeforeDays: DefaultRenewBeforeDays,
|
||||
ACMEIssuer: defaultIssuer,
|
||||
CredentialConfig: defaultCredentialConfig,
|
||||
Version: time.Now().Format("20060102030405"),
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func getRandEmail() string {
|
||||
num1 := rangeRandom(100, 100000)
|
||||
num2 := rangeRandom(100, 100000)
|
||||
return fmt.Sprintf("your%d@yours%d.com", num1, num2)
|
||||
}
|
||||
122
pkg/cert/config_test.go
Normal file
122
pkg/cert/config_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMatchSecretNameByDomain(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
domain string
|
||||
credentialCfg []CredentialEntry
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Exact match",
|
||||
domain: "example.com",
|
||||
credentialCfg: []CredentialEntry{
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
TLSSecret: "example-com-tls",
|
||||
},
|
||||
},
|
||||
expected: "example-com-tls",
|
||||
},
|
||||
|
||||
{
|
||||
name: "Exact match ignore case ",
|
||||
domain: "eXample.com",
|
||||
credentialCfg: []CredentialEntry{
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
TLSSecret: "example-com-tls",
|
||||
},
|
||||
},
|
||||
expected: "example-com-tls",
|
||||
},
|
||||
{
|
||||
name: "Wildcard match",
|
||||
domain: "sub.example.com",
|
||||
credentialCfg: []CredentialEntry{
|
||||
{
|
||||
Domains: []string{"*.example.com"},
|
||||
TLSSecret: "wildcard-example-com-tls",
|
||||
},
|
||||
},
|
||||
expected: "wildcard-example-com-tls",
|
||||
},
|
||||
|
||||
{
|
||||
name: "Wildcard match ignore case",
|
||||
domain: "sub.Example.com",
|
||||
credentialCfg: []CredentialEntry{
|
||||
{
|
||||
Domains: []string{"*.example.com"},
|
||||
TLSSecret: "wildcard-example-com-tls",
|
||||
},
|
||||
},
|
||||
expected: "wildcard-example-com-tls",
|
||||
},
|
||||
{
|
||||
name: "* match",
|
||||
domain: "blog.example.co.uk",
|
||||
credentialCfg: []CredentialEntry{
|
||||
{
|
||||
Domains: []string{"*"},
|
||||
TLSSecret: "blog-co-uk-tls",
|
||||
},
|
||||
},
|
||||
expected: "blog-co-uk-tls",
|
||||
},
|
||||
{
|
||||
name: "No match",
|
||||
domain: "unknown.com",
|
||||
credentialCfg: []CredentialEntry{
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
TLSSecret: "example-com-tls",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Multiple matches - first match wins",
|
||||
domain: "example.com",
|
||||
credentialCfg: []CredentialEntry{
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
TLSSecret: "example-com-tls",
|
||||
},
|
||||
{
|
||||
Domains: []string{"*.example.com"},
|
||||
TLSSecret: "wildcard-example-com-tls",
|
||||
},
|
||||
},
|
||||
expected: "example-com-tls",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := Config{CredentialConfig: tt.credentialCfg}
|
||||
result := cfg.MatchSecretNameByDomain(tt.domain)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
165
pkg/cert/controller.go
Normal file
165
pkg/cert/controller.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/informers"
|
||||
v1informer "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
)
|
||||
|
||||
const (
|
||||
workNum = 1
|
||||
maxRetry = 2
|
||||
configMapName = "higress-https"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
namespace string
|
||||
ConfigMapInformer v1informer.ConfigMapInformer
|
||||
client kubernetes.Interface
|
||||
queue workqueue.RateLimitingInterface
|
||||
configMgr *ConfigMgr
|
||||
server *Server
|
||||
certMgr *CertMgr
|
||||
factory informers.SharedInformerFactory
|
||||
}
|
||||
|
||||
func (c *Controller) addConfigmap(obj interface{}) {
|
||||
key, err := cache.MetaNamespaceKeyFunc(obj)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
namespace, name, _ := cache.SplitMetaNamespaceKey(key)
|
||||
if namespace != c.namespace || name != configMapName {
|
||||
return
|
||||
}
|
||||
c.enqueue(name)
|
||||
|
||||
}
|
||||
func (c *Controller) updateConfigmap(oldObj interface{}, newObj interface{}) {
|
||||
key, err := cache.MetaNamespaceKeyFunc(oldObj)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
namespace, name, _ := cache.SplitMetaNamespaceKey(key)
|
||||
if namespace != c.namespace || name != configMapName {
|
||||
return
|
||||
}
|
||||
if reflect.DeepEqual(oldObj, newObj) {
|
||||
return
|
||||
}
|
||||
c.enqueue(name)
|
||||
}
|
||||
|
||||
func (c *Controller) enqueue(name string) {
|
||||
c.queue.Add(name)
|
||||
}
|
||||
|
||||
func (c *Controller) cachesSynced() bool {
|
||||
return c.ConfigMapInformer.Informer().HasSynced()
|
||||
}
|
||||
|
||||
func (c *Controller) Run(stopCh <-chan struct{}) error {
|
||||
defer runtime.HandleCrash()
|
||||
defer c.queue.ShutDown()
|
||||
CertLog.Info("Waiting for informer caches to sync")
|
||||
c.factory.Start(stopCh)
|
||||
if ok := cache.WaitForCacheSync(stopCh, c.cachesSynced); !ok {
|
||||
return fmt.Errorf("failed to wait for caches to sync")
|
||||
}
|
||||
CertLog.Info("Starting controller")
|
||||
// Launch one workers to process configmap resources
|
||||
for i := 0; i < workNum; i++ {
|
||||
go wait.Until(c.worker, time.Minute, stopCh)
|
||||
}
|
||||
CertLog.Info("Started workers")
|
||||
<-stopCh
|
||||
CertLog.Info("Shutting down workers")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) worker() {
|
||||
for c.processNextItem() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) processNextItem() bool {
|
||||
item, shutdown := c.queue.Get()
|
||||
if shutdown {
|
||||
return false
|
||||
}
|
||||
defer c.queue.Done(item)
|
||||
key := item.(string)
|
||||
CertLog.Infof("controller process item:%s", key)
|
||||
err := c.syncConfigmap(key)
|
||||
if err != nil {
|
||||
c.handleError(key, err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Controller) syncConfigmap(key string) error {
|
||||
configmap, err := c.ConfigMapInformer.Lister().ConfigMaps(c.namespace).Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newConfig, err := c.configMgr.ParseConfigFromConfigmap(configmap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldConfig := c.configMgr.GetConfig()
|
||||
// reconcile old config and new config
|
||||
return c.certMgr.Reconcile(context.Background(), oldConfig, newConfig)
|
||||
}
|
||||
|
||||
func (c *Controller) handleError(key string, err error) {
|
||||
runtime.HandleError(err)
|
||||
CertLog.Errorf("%+v", err)
|
||||
c.queue.Forget(key)
|
||||
}
|
||||
|
||||
func NewController(client kubernetes.Interface, namespace string, certMgr *CertMgr, configMgr *ConfigMgr) (*Controller, error) {
|
||||
kubeInformerFactory := informers.NewSharedInformerFactoryWithOptions(client, 0, informers.WithNamespace(namespace))
|
||||
configmapInformer := kubeInformerFactory.Core().V1().ConfigMaps()
|
||||
c := &Controller{
|
||||
certMgr: certMgr,
|
||||
configMgr: configMgr,
|
||||
client: client,
|
||||
namespace: namespace,
|
||||
factory: kubeInformerFactory,
|
||||
ConfigMapInformer: configmapInformer,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ingressManage"),
|
||||
}
|
||||
|
||||
CertLog.Info("Setting up configmap informer event handlers")
|
||||
configmapInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: c.addConfigmap,
|
||||
UpdateFunc: c.updateConfigmap,
|
||||
})
|
||||
|
||||
return c, nil
|
||||
}
|
||||
158
pkg/cert/ingress.go
Normal file
158
pkg/cert/ingress.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez"
|
||||
"github.com/mholt/acmez/acme"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
IngressClassName = "higress"
|
||||
IngressServiceName = "higress-controller"
|
||||
IngressNamePefix = "higress-http-solver-"
|
||||
IngressPathPrefix = "/.well-known/acme-challenge/"
|
||||
IngressServicePort = 8889
|
||||
)
|
||||
|
||||
type IngressSolver struct {
|
||||
client kubernetes.Interface
|
||||
acmeIssuer *certmagic.ACMEIssuer
|
||||
solversMu sync.Mutex
|
||||
namespace string
|
||||
ingressDelay time.Duration
|
||||
}
|
||||
|
||||
func NewIngressSolver(namespace string, client kubernetes.Interface, acmeIssuer *certmagic.ACMEIssuer) (acmez.Solver, error) {
|
||||
solver := &IngressSolver{
|
||||
namespace: namespace,
|
||||
client: client,
|
||||
acmeIssuer: acmeIssuer,
|
||||
ingressDelay: 5 * time.Second,
|
||||
}
|
||||
return solver, nil
|
||||
}
|
||||
|
||||
func (s *IngressSolver) Present(_ context.Context, challenge acme.Challenge) error {
|
||||
CertLog.Infof("ingress solver present challenge:%+v", challenge)
|
||||
s.solversMu.Lock()
|
||||
defer s.solversMu.Unlock()
|
||||
ingressName := s.getIngressName(challenge)
|
||||
ingress := s.constructIngress(challenge)
|
||||
CertLog.Infof("update ingress name:%s, ingress:%v", ingressName, ingress)
|
||||
_, err := s.client.NetworkingV1().Ingresses(s.namespace).Get(context.Background(), ingressName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// create ingress
|
||||
_, err2 := s.client.NetworkingV1().Ingresses(s.namespace).Create(context.Background(), ingress, metav1.CreateOptions{})
|
||||
return err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
_, err1 := s.client.NetworkingV1().Ingresses(s.namespace).Update(context.Background(), ingress, metav1.UpdateOptions{})
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IngressSolver) Wait(ctx context.Context, challenge acme.Challenge) error {
|
||||
CertLog.Infof("ingress solver wait challenge:%+v", challenge)
|
||||
// wait for ingress ready
|
||||
if s.ingressDelay > 0 {
|
||||
select {
|
||||
case <-time.After(s.ingressDelay):
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
CertLog.Infof("ingress solver wait challenge done")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IngressSolver) CleanUp(_ context.Context, challenge acme.Challenge) error {
|
||||
CertLog.Infof("ingress solver cleanup challenge:%+v", challenge)
|
||||
s.solversMu.Lock()
|
||||
defer s.solversMu.Unlock()
|
||||
ingressName := s.getIngressName(challenge)
|
||||
CertLog.Infof("cleanup ingress name:%s", ingressName)
|
||||
err := s.client.NetworkingV1().Ingresses(s.namespace).Delete(context.Background(), ingressName, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IngressSolver) Delete(_ context.Context, challenge acme.Challenge) error {
|
||||
s.solversMu.Lock()
|
||||
defer s.solversMu.Unlock()
|
||||
err := s.client.NetworkingV1().Ingresses(s.namespace).Delete(context.Background(), s.getIngressName(challenge), metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *IngressSolver) getIngressName(challenge acme.Challenge) string {
|
||||
return IngressNamePefix + strings.ReplaceAll(challenge.Identifier.Value, ".", "-")
|
||||
}
|
||||
|
||||
func (s *IngressSolver) constructIngress(challenge acme.Challenge) *v1.Ingress {
|
||||
ingressClassName := IngressClassName
|
||||
ingressDomain := challenge.Identifier.Value
|
||||
ingressPath := IngressPathPrefix + challenge.Token
|
||||
ingress := v1.Ingress{}
|
||||
ingress.Name = s.getIngressName(challenge)
|
||||
ingress.Namespace = s.namespace
|
||||
pathType := v1.PathTypePrefix
|
||||
ingress.Spec = v1.IngressSpec{
|
||||
IngressClassName: &ingressClassName,
|
||||
Rules: []v1.IngressRule{
|
||||
{
|
||||
Host: ingressDomain,
|
||||
IngressRuleValue: v1.IngressRuleValue{
|
||||
HTTP: &v1.HTTPIngressRuleValue{
|
||||
Paths: []v1.HTTPIngressPath{
|
||||
{
|
||||
Path: ingressPath,
|
||||
PathType: &pathType,
|
||||
Backend: v1.IngressBackend{
|
||||
Service: &v1.IngressServiceBackend{
|
||||
Name: IngressServiceName,
|
||||
Port: v1.ServiceBackendPort{
|
||||
Number: IngressServicePort,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &ingress
|
||||
}
|
||||
19
pkg/cert/log.go
Normal file
19
pkg/cert/log.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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 cert
|
||||
|
||||
import "istio.io/pkg/log"
|
||||
|
||||
var CertLog = log.RegisterScope("cert", "Higress Cert process.", 0)
|
||||
108
pkg/cert/secret.go
Normal file
108
pkg/cert/secret.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
SecretNamePrefix = "higress-secret-"
|
||||
)
|
||||
|
||||
type SecretMgr struct {
|
||||
client kubernetes.Interface
|
||||
namespace string
|
||||
}
|
||||
|
||||
func NewSecretMgr(namespace string, client kubernetes.Interface) (*SecretMgr, error) {
|
||||
secretMgr := &SecretMgr{
|
||||
namespace: namespace,
|
||||
client: client,
|
||||
}
|
||||
|
||||
return secretMgr, nil
|
||||
}
|
||||
|
||||
func (s *SecretMgr) Update(domain string, secretName string, privateKey []byte, certificate []byte, notBefore time.Time, notAfter time.Time, isRenew bool) error {
|
||||
//secretName := s.getSecretName(domain)
|
||||
secret := s.constructSecret(domain, privateKey, certificate, notBefore, notAfter, isRenew)
|
||||
_, err := s.client.CoreV1().Secrets(s.namespace).Get(context.Background(), secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// create secret
|
||||
_, err2 := s.client.CoreV1().Secrets(s.namespace).Create(context.Background(), secret, metav1.CreateOptions{})
|
||||
return err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
// check secret annotations
|
||||
if _, ok := secret.Annotations["higress.io/cert-domain"]; !ok {
|
||||
return fmt.Errorf("the secret name %s is not automatic https secret name for the domain:%s, please rename it in config", secretName, domain)
|
||||
}
|
||||
_, err1 := s.client.CoreV1().Secrets(s.namespace).Update(context.Background(), secret, metav1.UpdateOptions{})
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SecretMgr) Delete(domain string) error {
|
||||
secretName := s.getSecretName(domain)
|
||||
err := s.client.CoreV1().Secrets(s.namespace).Delete(context.Background(), secretName, metav1.DeleteOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SecretMgr) getSecretName(domain string) string {
|
||||
return SecretNamePrefix + strings.ReplaceAll(strings.TrimSpace(domain), ".", "-")
|
||||
}
|
||||
|
||||
func (s *SecretMgr) constructSecret(domain string, privateKey []byte, certificate []byte, notBefore time.Time, notAfter time.Time, isRenew bool) *v1.Secret {
|
||||
secretName := s.getSecretName(domain)
|
||||
annotationMap := make(map[string]string, 0)
|
||||
annotationMap["higress.io/cert-domain"] = domain
|
||||
annotationMap["higress.io/cert-notAfter"] = notAfter.Format("2006-01-02 15:04:05")
|
||||
annotationMap["higress.io/cert-notBefore"] = notBefore.Format("2006-01-02 15:04:05")
|
||||
annotationMap["higress.io/cert-renew"] = strconv.FormatBool(isRenew)
|
||||
if isRenew {
|
||||
annotationMap["higress.io/cert-renew-time"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
// Required fields:
|
||||
// - Secret.Data["tls.key"] - TLS private key.
|
||||
// Secret.Data["tls.crt"] - TLS certificate.
|
||||
dataMap := make(map[string][]byte, 0)
|
||||
dataMap["tls.key"] = privateKey
|
||||
dataMap["tls.crt"] = certificate
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: s.namespace,
|
||||
Annotations: annotationMap,
|
||||
},
|
||||
Type: v1.SecretTypeTLS,
|
||||
Data: dataMap,
|
||||
}
|
||||
return secret
|
||||
}
|
||||
115
pkg/cert/server.go
Normal file
115
pkg/cert/server.go
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Namespace string
|
||||
ServerAddress string
|
||||
Email string
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
httpServer *http.Server
|
||||
opts *Option
|
||||
clientSet kubernetes.Interface
|
||||
controller *Controller
|
||||
certMgr *CertMgr
|
||||
}
|
||||
|
||||
func NewServer(clientSet kubernetes.Interface, opts *Option) (*Server, error) {
|
||||
server := &Server{
|
||||
clientSet: clientSet,
|
||||
opts: opts,
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (s *Server) InitDefaultConfig() error {
|
||||
configMgr, _ := NewConfigMgr(s.opts.Namespace, s.clientSet)
|
||||
// init config if there is not existed
|
||||
_, err := configMgr.InitConfig(s.opts.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) InitServer() error {
|
||||
configMgr, _ := NewConfigMgr(s.opts.Namespace, s.clientSet)
|
||||
// init config if there is not existed
|
||||
defaultConfig, err := configMgr.InitConfig(s.opts.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// init certmgr
|
||||
certMgr, err := InitCertMgr(s.opts, s.clientSet, defaultConfig) // config and start
|
||||
s.certMgr = certMgr
|
||||
// init controller
|
||||
controller, err := NewController(s.clientSet, s.opts.Namespace, certMgr, configMgr)
|
||||
s.controller = controller
|
||||
// init http server
|
||||
s.initHttpServer()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) initHttpServer() error {
|
||||
CertLog.Infof("server init http server")
|
||||
ctx := context.Background()
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Lookit my cool website over HTTPS!")
|
||||
})
|
||||
httpServer := &http.Server{
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
IdleTimeout: 5 * time.Second,
|
||||
Addr: s.opts.ServerAddress,
|
||||
BaseContext: func(listener net.Listener) context.Context { return ctx },
|
||||
}
|
||||
cfg := s.certMgr.cfg
|
||||
if len(cfg.Issuers) > 0 {
|
||||
if am, ok := cfg.Issuers[0].(*certmagic.ACMEIssuer); ok {
|
||||
httpServer.Handler = am.HTTPChallengeHandler(mux)
|
||||
}
|
||||
} else {
|
||||
httpServer.Handler = mux
|
||||
}
|
||||
s.httpServer = httpServer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Run(stopCh <-chan struct{}) error {
|
||||
go s.controller.Run(stopCh)
|
||||
CertLog.Infof("server run")
|
||||
go func() {
|
||||
<-stopCh
|
||||
CertLog.Infof("server http server shutdown now...")
|
||||
s.httpServer.Shutdown(context.Background())
|
||||
}()
|
||||
err := s.httpServer.ListenAndServe()
|
||||
return err
|
||||
}
|
||||
337
pkg/cert/storage.go
Normal file
337
pkg/cert/storage.go
Normal file
@@ -0,0 +1,337 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
const (
|
||||
CertificatesPrefix = "/certificates"
|
||||
ConfigmapStoreCertficatesPrefix = "higress-cert-store-certificates-"
|
||||
ConfigmapStoreDefaultName = "higress-cert-store-default"
|
||||
)
|
||||
|
||||
var _ certmagic.Storage = (*ConfigmapStorage)(nil)
|
||||
|
||||
type ConfigmapStorage struct {
|
||||
namespace string
|
||||
client kubernetes.Interface
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
type HashValue struct {
|
||||
K string `json:"k,omitempty"`
|
||||
V []byte `json:"v,omitempty"`
|
||||
}
|
||||
|
||||
func NewConfigmapStorage(namespace string, client kubernetes.Interface) (certmagic.Storage, error) {
|
||||
storage := &ConfigmapStorage{
|
||||
namespace: namespace,
|
||||
client: client,
|
||||
}
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
// Exists returns true if key exists in s.
|
||||
func (s *ConfigmapStorage) Exists(_ context.Context, key string) bool {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
cm, err := s.getConfigmapStoreByKey(key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if cm.Data == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
hashKey := fastHash([]byte(key))
|
||||
if _, ok := cm.Data[hashKey]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Store saves value at key.
|
||||
func (s *ConfigmapStorage) Store(_ context.Context, key string, value []byte) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
cm, err := s.getConfigmapStoreByKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cm.Data == nil {
|
||||
cm.Data = make(map[string]string, 0)
|
||||
}
|
||||
|
||||
hashKey := fastHash([]byte(key))
|
||||
hashV := &HashValue{
|
||||
K: key,
|
||||
V: value,
|
||||
}
|
||||
bytes, err := json.Marshal(hashV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.Data[hashKey] = string(bytes)
|
||||
return s.updateConfigmap(cm)
|
||||
}
|
||||
|
||||
// Load retrieves the value at key.
|
||||
func (s *ConfigmapStorage) Load(_ context.Context, key string) ([]byte, error) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
var value []byte
|
||||
cm, err := s.getConfigmapStoreByKey(key)
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
if cm.Data == nil {
|
||||
return value, fs.ErrNotExist
|
||||
}
|
||||
|
||||
hashKey := fastHash([]byte(key))
|
||||
if v, ok := cm.Data[hashKey]; ok {
|
||||
hV := &HashValue{}
|
||||
err = json.Unmarshal([]byte(v), hV)
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
return hV.V, nil
|
||||
}
|
||||
return value, fs.ErrNotExist
|
||||
}
|
||||
|
||||
// Delete deletes the value at key.
|
||||
func (s *ConfigmapStorage) Delete(_ context.Context, key string) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
cm, err := s.getConfigmapStoreByKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cm.Data == nil {
|
||||
cm.Data = make(map[string]string, 0)
|
||||
}
|
||||
hashKey := fastHash([]byte(key))
|
||||
delete(cm.Data, hashKey)
|
||||
return s.updateConfigmap(cm)
|
||||
}
|
||||
|
||||
// List returns all keys that match the prefix.
|
||||
// If the prefix is "/certificates", it retrieves all ConfigMaps, otherwise only one.
|
||||
func (s *ConfigmapStorage) List(ctx context.Context, prefix string, recursive bool) ([]string, error) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
var keys []string
|
||||
var configmapKeys []string
|
||||
visitedDirs := make(map[string]struct{})
|
||||
|
||||
// Check if the prefix corresponds to a specific key
|
||||
hashPrefix := fastHash([]byte(prefix))
|
||||
if strings.HasPrefix(prefix, CertificatesPrefix) {
|
||||
// If the prefix is "/certificates", get all ConfigMaps and traverse each one
|
||||
// List all ConfigMaps in the namespace with label higress.io/cert-https=true
|
||||
configmaps, err := s.client.CoreV1().ConfigMaps(s.namespace).List(ctx, metav1.ListOptions{FieldSelector: "metadata.annotations['higress.io/cert-https'] == 'true'"})
|
||||
if err != nil {
|
||||
return keys, err
|
||||
}
|
||||
|
||||
for _, cm := range configmaps.Items {
|
||||
// Check if the ConfigMap name starts with the expected prefix
|
||||
if strings.HasPrefix(cm.Name, ConfigmapStoreCertficatesPrefix) {
|
||||
// Add the keys from Data field to the list
|
||||
for _, v := range cm.Data {
|
||||
// Unmarshal the value into hashValue struct
|
||||
var hv HashValue
|
||||
if err := json.Unmarshal([]byte(v), &hv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check if the key starts with the specified prefix
|
||||
if strings.HasPrefix(hv.K, prefix) {
|
||||
// Add the key to the list
|
||||
configmapKeys = append(configmapKeys, hv.K)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If not starting with "/certificates", get the specific ConfigMap
|
||||
cm, err := s.getConfigmapStoreByKey(prefix)
|
||||
if err != nil {
|
||||
return keys, err
|
||||
}
|
||||
|
||||
if _, ok := cm.Data[hashPrefix]; ok {
|
||||
// The prefix corresponds to a specific key, add it to the list
|
||||
configmapKeys = append(configmapKeys, prefix)
|
||||
} else {
|
||||
// The prefix is considered a directory
|
||||
for _, v := range cm.Data {
|
||||
// Unmarshal the value into hashValue struct
|
||||
var hv HashValue
|
||||
if err := json.Unmarshal([]byte(v), &hv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Check if the key starts with the specified prefix
|
||||
if strings.HasPrefix(hv.K, prefix) {
|
||||
// Add the key to the list
|
||||
configmapKeys = append(configmapKeys, hv.K)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return all
|
||||
if recursive {
|
||||
return configmapKeys, nil
|
||||
}
|
||||
|
||||
// only return sub dirs
|
||||
for _, key := range configmapKeys {
|
||||
subPath := strings.TrimPrefix(strings.ReplaceAll(key, prefix, ""), "/")
|
||||
paths := strings.Split(subPath, "/")
|
||||
if len(paths) > 0 {
|
||||
subDir := path.Join(prefix, paths[0])
|
||||
if _, ok := visitedDirs[subDir]; !ok {
|
||||
keys = append(keys, subDir)
|
||||
}
|
||||
visitedDirs[subDir] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// Stat returns information about key. only support for no certificates path
|
||||
func (s *ConfigmapStorage) Stat(_ context.Context, key string) (certmagic.KeyInfo, error) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
// Create a new KeyInfo struct
|
||||
info := certmagic.KeyInfo{}
|
||||
|
||||
// Get the ConfigMap containing the keys
|
||||
cm, err := s.getConfigmapStoreByKey(key)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
// Check if the key exists in the ConfigMap
|
||||
hashKey := fastHash([]byte(key))
|
||||
if data, ok := cm.Data[hashKey]; ok {
|
||||
// The key exists, populate the KeyInfo struct
|
||||
info.Key = key
|
||||
info.Modified = time.Now() // Since we're not tracking modification time in ConfigMap
|
||||
info.Size = int64(len(data))
|
||||
info.IsTerminal = true
|
||||
} else {
|
||||
// Check if there are other keys with the same prefix
|
||||
prefixKeys := make([]string, 0)
|
||||
for _, v := range cm.Data {
|
||||
var hv HashValue
|
||||
if err := json.Unmarshal([]byte(v), &hv); err != nil {
|
||||
return info, err
|
||||
}
|
||||
// Check if the key starts with the specified prefix
|
||||
if strings.HasPrefix(hv.K, key) {
|
||||
// Add the key to the list
|
||||
prefixKeys = append(prefixKeys, hv.K)
|
||||
}
|
||||
}
|
||||
// If there are multiple keys with the same prefix, then it's not a terminal node
|
||||
if len(prefixKeys) > 0 {
|
||||
info.Key = key
|
||||
info.IsTerminal = false
|
||||
} else {
|
||||
return info, fmt.Errorf("prefix '%s' is not existed", key)
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Lock obtains a lock named by the given name. It blocks
|
||||
// until the lock can be obtained or an error is returned.
|
||||
func (s *ConfigmapStorage) Lock(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock releases the lock for name.
|
||||
func (s *ConfigmapStorage) Unlock(_ context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ConfigmapStorage) String() string {
|
||||
return "ConfigmapStorage"
|
||||
}
|
||||
|
||||
func (s *ConfigmapStorage) getConfigmapStoreNameByKey(key string) string {
|
||||
parts := strings.SplitN(key, "/", 10)
|
||||
if len(parts) >= 4 && parts[1] == "certificates" {
|
||||
domain := strings.TrimSuffix(parts[3], ".crt")
|
||||
domain = strings.TrimSuffix(domain, ".key")
|
||||
domain = strings.TrimSuffix(domain, ".json")
|
||||
issuerKey := parts[2]
|
||||
return ConfigmapStoreCertficatesPrefix + fastHash([]byte(issuerKey+domain))
|
||||
}
|
||||
return ConfigmapStoreDefaultName
|
||||
}
|
||||
|
||||
func (s *ConfigmapStorage) getConfigmapStoreByKey(key string) (*v1.ConfigMap, error) {
|
||||
configmapName := s.getConfigmapStoreNameByKey(key)
|
||||
cm, err := s.client.CoreV1().ConfigMaps(s.namespace).Get(context.Background(), configmapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// Save default ConfigMap
|
||||
cm = &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: s.namespace,
|
||||
Name: configmapName,
|
||||
Annotations: map[string]string{"higress.io/cert-https": "true"},
|
||||
},
|
||||
}
|
||||
_, err = s.client.CoreV1().ConfigMaps(s.namespace).Create(context.Background(), cm, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
// updateConfigmap adds or updates the annotation higress.io/cert-https to true.
|
||||
func (s *ConfigmapStorage) updateConfigmap(configmap *v1.ConfigMap) error {
|
||||
if configmap.ObjectMeta.Annotations == nil {
|
||||
configmap.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
configmap.ObjectMeta.Annotations["higress.io/cert-https"] = "true"
|
||||
|
||||
_, err := s.client.CoreV1().ConfigMaps(configmap.Namespace).Update(context.Background(), configmap, metav1.UpdateOptions{})
|
||||
return err
|
||||
}
|
||||
325
pkg/cert/storage_test.go
Normal file
325
pkg/cert/storage_test.go
Normal file
@@ -0,0 +1,325 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestGetConfigmapStoreNameByKey(t *testing.T) {
|
||||
// Create a fake client for testing
|
||||
fakeClient := fake.NewSimpleClientset()
|
||||
// Create a new ConfigmapStorage instance for testing
|
||||
namespace := "your-namespace"
|
||||
storage := &ConfigmapStorage{
|
||||
namespace: namespace,
|
||||
client: fakeClient,
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "certificate crt",
|
||||
key: "/certificates/issuerKey/domain.crt",
|
||||
expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")),
|
||||
},
|
||||
{
|
||||
name: "certificate meta",
|
||||
key: "/certificates/issuerKey/domain.json",
|
||||
expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")),
|
||||
},
|
||||
{
|
||||
name: "certificate key",
|
||||
key: "/certificates/issuerKey/domain.key",
|
||||
expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")),
|
||||
},
|
||||
{
|
||||
name: "user key",
|
||||
key: "/users/hello/2",
|
||||
expected: "higress-cert-store-default",
|
||||
},
|
||||
{
|
||||
name: "Empty Key",
|
||||
key: "",
|
||||
expected: "higress-cert-store-default",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
storageName := storage.getConfigmapStoreNameByKey(test.key)
|
||||
assert.Equal(t, test.expected, storageName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExists(t *testing.T) {
|
||||
// Create a fake client for testing
|
||||
fakeClient := fake.NewSimpleClientset()
|
||||
|
||||
// Create a new ConfigmapStorage instance for testing
|
||||
namespace := "your-namespace"
|
||||
storage, err := NewConfigmapStorage(namespace, fakeClient)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Store a test key
|
||||
testKey := "/certificates/issuer1/domain1.crt"
|
||||
err = storage.Store(context.Background(), testKey, []byte("test-data"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
shouldExist bool
|
||||
}{
|
||||
{
|
||||
name: "Existing Key",
|
||||
key: "/certificates/issuer1/domain1.crt",
|
||||
shouldExist: true,
|
||||
},
|
||||
{
|
||||
name: "Non-Existent Key1",
|
||||
key: "/certificates/issuer2/domain2.crt",
|
||||
shouldExist: false,
|
||||
},
|
||||
{
|
||||
name: "Non-Existent Key2",
|
||||
key: "/users/hello/a",
|
||||
shouldExist: false,
|
||||
},
|
||||
// Add more test cases as needed
|
||||
}
|
||||
|
||||
// Run tests
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
exists := storage.Exists(context.Background(), test.key)
|
||||
assert.Equal(t, test.shouldExist, exists)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
// Create a fake client for testing
|
||||
fakeClient := fake.NewSimpleClientset()
|
||||
|
||||
// Create a new ConfigmapStorage instance for testing
|
||||
namespace := "your-namespace"
|
||||
storage, err := NewConfigmapStorage(namespace, fakeClient)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Store a test key
|
||||
testKey := "/certificates/issuer1/domain1.crt"
|
||||
testValue := []byte("test-data")
|
||||
err = storage.Store(context.Background(), testKey, testValue)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
expected []byte
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "Existing Key",
|
||||
key: "/certificates/issuer1/domain1.crt",
|
||||
expected: testValue,
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Non-Existent Key",
|
||||
key: "/certificates/issuer2/domain2.crt",
|
||||
expected: nil,
|
||||
shouldError: true,
|
||||
},
|
||||
// Add more test cases as needed
|
||||
}
|
||||
|
||||
// Run tests
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
value, err := storage.Load(context.Background(), test.key)
|
||||
if test.shouldError {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, value)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
// Create a fake client for testing
|
||||
fakeClient := fake.NewSimpleClientset()
|
||||
|
||||
// Create a new ConfigmapStorage instance for testing
|
||||
namespace := "your-namespace"
|
||||
storage := ConfigmapStorage{
|
||||
namespace: namespace,
|
||||
client: fakeClient,
|
||||
}
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
value []byte
|
||||
expected map[string]string
|
||||
expectedConfigmapName string
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "Store Key with /certificates prefix",
|
||||
key: "/certificates/issuer1/domain1.crt",
|
||||
value: []byte("test-data1"),
|
||||
expected: map[string]string{fastHash([]byte("/certificates/issuer1/domain1.crt")): `{"k":"/certificates/issuer1/domain1.crt","v":"dGVzdC1kYXRhMQ=="}`},
|
||||
expectedConfigmapName: "higress-cert-store-certificates-" + fastHash([]byte("issuer1"+"domain1")),
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Store Key with /certificates prefix (additional data)",
|
||||
key: "/certificates/issuer2/domain2.crt",
|
||||
value: []byte("test-data2"),
|
||||
expected: map[string]string{
|
||||
fastHash([]byte("/certificates/issuer2/domain2.crt")): `{"k":"/certificates/issuer2/domain2.crt","v":"dGVzdC1kYXRhMg=="}`,
|
||||
},
|
||||
expectedConfigmapName: "higress-cert-store-certificates-" + fastHash([]byte("issuer2"+"domain2")),
|
||||
shouldError: false,
|
||||
},
|
||||
{
|
||||
name: "Store Key without /certificates prefix",
|
||||
key: "/other/path/data.txt",
|
||||
value: []byte("test-data3"),
|
||||
expected: map[string]string{fastHash([]byte("/other/path/data.txt")): `{"k":"/other/path/data.txt","v":"dGVzdC1kYXRhMw=="}`},
|
||||
expectedConfigmapName: "higress-cert-store-default",
|
||||
shouldError: false,
|
||||
},
|
||||
// Add more test cases as needed
|
||||
}
|
||||
|
||||
// Run tests
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := storage.Store(context.Background(), test.key, test.value)
|
||||
if test.shouldError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check the contents of the ConfigMap after storing
|
||||
configmapName := storage.getConfigmapStoreNameByKey(test.key)
|
||||
cm, err := fakeClient.CoreV1().ConfigMaps(namespace).Get(context.Background(), configmapName, metav1.GetOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if the data is as expected
|
||||
assert.Equal(t, test.expected, cm.Data)
|
||||
|
||||
// Check if the configmapName is correct
|
||||
assert.Equal(t, test.expectedConfigmapName, configmapName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
// Create a fake client for testing
|
||||
fakeClient := fake.NewSimpleClientset()
|
||||
|
||||
// Create a new ConfigmapStorage instance for testing
|
||||
namespace := "your-namespace"
|
||||
storage, err := NewConfigmapStorage(namespace, fakeClient)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Store some test data
|
||||
// Store some test data
|
||||
testKeys := []string{
|
||||
"/certificates/issuer1/domain1.crt",
|
||||
"/certificates/issuer1/domain2.crt",
|
||||
"/certificates/issuer1/domain3.crt", // Added another domain for issuer1
|
||||
"/certificates/issuer2/domain4.crt",
|
||||
"/certificates/issuer2/domain5.crt",
|
||||
"/certificates/issuer3/subdomain1/domain6.crt", // Two-level subdirectory under issuer3
|
||||
"/certificates/issuer3/subdomain1/subdomain2/domain7.crt", // Two more levels under issuer3
|
||||
"/other-prefix/key1/file1",
|
||||
"/other-prefix/key1/file2",
|
||||
"/other-prefix/key2/file3",
|
||||
"/other-prefix/key2/file4",
|
||||
}
|
||||
|
||||
for _, key := range testKeys {
|
||||
err := storage.Store(context.Background(), key, []byte("test-data"))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// Define test cases
|
||||
tests := []struct {
|
||||
name string
|
||||
prefix string
|
||||
recursive bool
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "List Certificates (Non-Recursive)",
|
||||
prefix: "/certificates",
|
||||
recursive: false,
|
||||
expected: []string{"/certificates/issuer1", "/certificates/issuer2", "/certificates/issuer3"},
|
||||
},
|
||||
{
|
||||
name: "List Certificates (Recursive)",
|
||||
prefix: "/certificates",
|
||||
recursive: true,
|
||||
expected: []string{"/certificates/issuer1/domain1.crt", "/certificates/issuer1/domain2.crt", "/certificates/issuer1/domain3.crt", "/certificates/issuer2/domain4.crt", "/certificates/issuer2/domain5.crt", "/certificates/issuer3/subdomain1/domain6.crt", "/certificates/issuer3/subdomain1/subdomain2/domain7.crt"},
|
||||
},
|
||||
{
|
||||
name: "List Other Prefix (Non-Recursive)",
|
||||
prefix: "/other-prefix",
|
||||
recursive: false,
|
||||
expected: []string{"/other-prefix/key1", "/other-prefix/key2"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "List Other Prefix (Non-Recursive)",
|
||||
prefix: "/other-prefix/key1",
|
||||
recursive: false,
|
||||
expected: []string{"/other-prefix/key1/file1", "/other-prefix/key1/file2"},
|
||||
},
|
||||
{
|
||||
name: "List Other Prefix (Recursive)",
|
||||
prefix: "/other-prefix",
|
||||
recursive: true,
|
||||
expected: []string{"/other-prefix/key1/file1", "/other-prefix/key1/file2", "/other-prefix/key2/file3", "/other-prefix/key2/file4"},
|
||||
},
|
||||
}
|
||||
|
||||
// Run tests
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
keys, err := storage.List(context.Background(), test.prefix, test.recursive)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, test.expected, keys)
|
||||
})
|
||||
}
|
||||
}
|
||||
97
pkg/cert/util.go
Normal file
97
pkg/cert/util.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// 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 cert
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math/rand"
|
||||
"net"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// parseCertsFromPEMBundle parses a certificate bundle from top to bottom and returns
|
||||
// a slice of x509 certificates. This function will error if no certificates are found.
|
||||
func parseCertsFromPEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||
var certificates []*x509.Certificate
|
||||
var certDERBlock *pem.Block
|
||||
for {
|
||||
certDERBlock, bundle = pem.Decode(bundle)
|
||||
if certDERBlock == nil {
|
||||
break
|
||||
}
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificates = append(certificates, cert)
|
||||
}
|
||||
}
|
||||
if len(certificates) == 0 {
|
||||
return nil, fmt.Errorf("no certificates found in bundle")
|
||||
}
|
||||
return certificates, nil
|
||||
}
|
||||
|
||||
func notAfter(cert *x509.Certificate) time.Time {
|
||||
if cert == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return cert.NotAfter.Truncate(time.Second).Add(1 * time.Second)
|
||||
}
|
||||
|
||||
func notBefore(cert *x509.Certificate) time.Time {
|
||||
if cert == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return cert.NotBefore.Truncate(time.Second).Add(1 * time.Second)
|
||||
}
|
||||
|
||||
// hostOnly returns only the host portion of hostport.
|
||||
// If there is no port or if there is an error splitting
|
||||
// the port off, the whole input string is returned.
|
||||
func hostOnly(hostport string) string {
|
||||
host, _, err := net.SplitHostPort(hostport)
|
||||
if err != nil {
|
||||
return hostport // OK; probably had no port to begin with
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func rangeRandom(min, max int) (number int) {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
number = r.Intn(max-min) + min
|
||||
return number
|
||||
}
|
||||
|
||||
func ValidateEmail(email string) bool {
|
||||
pattern := `^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`
|
||||
regExp := regexp.MustCompile(pattern)
|
||||
if regExp.MatchString(email) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func fastHash(input []byte) string {
|
||||
h := fnv.New32a()
|
||||
h.Write(input)
|
||||
return fmt.Sprintf("%x", h.Sum32())
|
||||
}
|
||||
@@ -75,9 +75,15 @@ static_resources:
|
||||
stat_prefix: ingress_http
|
||||
# Output envoy logs to stdout
|
||||
access_log:
|
||||
- name: envoy.access_loggers.stdout
|
||||
- name: envoy.access_loggers.file
|
||||
filter:
|
||||
not_health_check_filter: {}
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
|
||||
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
|
||||
path: /dev/stdout
|
||||
log_format:
|
||||
text_format_source:
|
||||
inline_string: "{\"authority\":\"%REQ(X-ENVOY-ORIGINAL-HOST?:AUTHORITY)%\",\"bytes_received\":\"%BYTES_RECEIVED%\",\"bytes_sent\":\"%BYTES_SENT%\",\"downstream_local_address\":\"%DOWNSTREAM_LOCAL_ADDRESS%\",\"downstream_remote_address\":\"%DOWNSTREAM_REMOTE_ADDRESS%\",\"duration\":\"%DURATION%\",\"istio_policy_status\":\"%DYNAMIC_METADATA(istio.mixer:status)%\",\"method\":\"%REQ(:METHOD)%\",\"path\":\"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%\",\"protocol\":\"%PROTOCOL%\",\"request_id\":\"%REQ(X-REQUEST-ID)%\",\"requested_server_name\":\"%REQUESTED_SERVER_NAME%\",\"response_code\":\"%RESPONSE_CODE%\",\"response_flags\":\"%RESPONSE_FLAGS%\",\"route_name\":\"%ROUTE_NAME%\",\"start_time\":\"%START_TIME%\",\"trace_id\":\"%REQ(X-B3-TRACEID)%\",\"upstream_cluster\":\"%UPSTREAM_CLUSTER%\",\"upstream_host\":\"%UPSTREAM_HOST%\",\"upstream_local_address\":\"%UPSTREAM_LOCAL_ADDRESS%\",\"upstream_service_time\":\"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%\",\"upstream_transport_failure_reason\":\"%UPSTREAM_TRANSPORT_FAILURE_REASON%\",\"user_agent\":\"%REQ(USER-AGENT)%\",\"x_forwarded_for\":\"%REQ(X-FORWARDED-FOR)%\"}\n"
|
||||
# Modify as required
|
||||
route_config:
|
||||
name: local_route
|
||||
|
||||
@@ -124,7 +124,7 @@ func (s *JSONSchemaPropsOrBool) UnmarshalJSON(data []byte) error {
|
||||
|
||||
func (s JSONSchemaPropsOrBool) MarshalYAML() (interface{}, error) {
|
||||
if s.Schema != nil {
|
||||
return yaml.Marshal(s.Schema)
|
||||
return s.Schema, nil
|
||||
}
|
||||
|
||||
if s.Schema == nil && !s.Allows {
|
||||
|
||||
@@ -33,7 +33,7 @@ type WasmPluginMeta struct {
|
||||
Spec WasmPluginSpec `json:"spec" yaml:"spec"`
|
||||
}
|
||||
|
||||
func defaultWsamPluginMeta() *WasmPluginMeta {
|
||||
func defaultWasmPluginMeta() *WasmPluginMeta {
|
||||
return &WasmPluginMeta{
|
||||
APIVersion: "1.0.0",
|
||||
Info: WasmPluginInfo{
|
||||
@@ -77,7 +77,7 @@ func ParseGoSrc(dir, model string) (*WasmPluginMeta, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta := defaultWsamPluginMeta()
|
||||
meta := defaultWasmPluginMeta()
|
||||
meta.setByConfigModel(m)
|
||||
return meta, nil
|
||||
}
|
||||
@@ -96,26 +96,33 @@ func recursiveSetSchema(model *Model, parent *JSONSchemaProps) (string, *JSONSch
|
||||
}
|
||||
newName := cur.HandleFieldTags(model.Tag, parent, model.Name)
|
||||
if IsArray(model.Type) {
|
||||
item := NewJSONSchemaProps()
|
||||
item.Type = GetItemType(cur.Type)
|
||||
cur.Type = "array"
|
||||
if IsObject(item.Type) {
|
||||
item.Properties = make(map[string]JSONSchemaProps)
|
||||
for _, field := range model.Fields {
|
||||
name, child := recursiveSetSchema(&field, cur)
|
||||
item.Properties[name] = *child
|
||||
}
|
||||
}
|
||||
cur.Items = &JSONSchemaPropsOrArray{Schema: item}
|
||||
itemModel := &*model
|
||||
itemModel.Type = GetItemType(model.Type)
|
||||
_, itemSchema := recursiveSetSchema(itemModel, nil)
|
||||
cur.Items = &JSONSchemaPropsOrArray{Schema: itemSchema}
|
||||
} else if IsMap(model.Type) {
|
||||
cur.Type = "object"
|
||||
valueModel := &*model
|
||||
valueModel.Type = GetValueType(model.Type)
|
||||
valueModel.Tag = ""
|
||||
valueModel.Doc = ""
|
||||
_, valueSchema := recursiveSetSchema(valueModel, nil)
|
||||
cur.AdditionalProperties = &JSONSchemaPropsOrBool{Schema: valueSchema}
|
||||
} else if IsObject(model.Type) { // type may be `array of object`, and it is handled in the first branch
|
||||
for _, field := range model.Fields {
|
||||
name, child := recursiveSetSchema(&field, cur)
|
||||
cur.Properties[name] = *child
|
||||
}
|
||||
cur.Properties = make(map[string]JSONSchemaProps)
|
||||
recursiveObjectProperties(cur, model)
|
||||
}
|
||||
return newName, cur
|
||||
}
|
||||
|
||||
func recursiveObjectProperties(parent *JSONSchemaProps, model *Model) {
|
||||
for _, field := range model.Fields {
|
||||
name, child := recursiveSetSchema(&field, parent)
|
||||
parent.Properties[name] = *child
|
||||
}
|
||||
}
|
||||
|
||||
func (meta *WasmPluginMeta) setModelAnnotations(comment string) {
|
||||
as := GetAnnotations(comment)
|
||||
for _, a := range as {
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
|
||||
const (
|
||||
ArrayPrefix = "array of "
|
||||
MapPrefix = "map of "
|
||||
ObjectSuffix = "object"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,23 @@ func IsArray(typ string) bool {
|
||||
|
||||
// GetItemType returns the item type of array, e.g.: array of int -> int
|
||||
func GetItemType(typ string) string {
|
||||
return strings.TrimPrefix(typ, ArrayPrefix)
|
||||
if !IsArray(typ) {
|
||||
return typ
|
||||
}
|
||||
return typ[len(ArrayPrefix):]
|
||||
}
|
||||
|
||||
// IsMap returns true if the given type is a `map of <type>`
|
||||
func IsMap(typ string) bool {
|
||||
return strings.HasPrefix(typ, MapPrefix)
|
||||
}
|
||||
|
||||
// GetValueType returns the value type of map, e.g.: map of int -> int
|
||||
func GetValueType(typ string) string {
|
||||
if !IsMap(typ) {
|
||||
return typ
|
||||
}
|
||||
return typ[len(MapPrefix):]
|
||||
}
|
||||
|
||||
// IsObject returns true if the given type is an `object` or an `array of object`
|
||||
@@ -259,7 +276,7 @@ func (p *ModelParser) parseModelFields(model string) (fields []Model, err error)
|
||||
return nil, errors.Wrapf(err, "failed to parse type %q of the field %q", field.Type, fd.Name)
|
||||
}
|
||||
if IsObject(fd.Type) {
|
||||
subModel, err := p.getModelName(field.Type)
|
||||
subModel, err := p.doGetModelName(pkgName, field.Type)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get the sub-model name of the field %q with type %q", fd.Name, field.Type)
|
||||
}
|
||||
@@ -313,6 +330,8 @@ func (p *ModelParser) doGetModelName(pkgName string, typ ast.Expr) (string, erro
|
||||
return p.doGetModelName(pkgName, t.X)
|
||||
case *ast.ArrayType: // slice or array
|
||||
return p.doGetModelName(pkgName, t.Elt)
|
||||
case *ast.MapType:
|
||||
return p.doGetModelName(pkgName, t.Value)
|
||||
case *ast.SelectorExpr: // <pkg_name>.<field_name>
|
||||
pkg, ok := t.X.(*ast.Ident)
|
||||
if !ok {
|
||||
@@ -339,6 +358,16 @@ func (p *ModelParser) parseFieldType(pkgName string, typ ast.Expr) (string, erro
|
||||
return "", err
|
||||
}
|
||||
return ArrayPrefix + ret, nil
|
||||
case *ast.MapType:
|
||||
if keyIdent, ok := t.Key.(*ast.Ident); !ok {
|
||||
return "", ErrInvalidFieldType
|
||||
} else if keyIdent.Name != "string" {
|
||||
return "", ErrInvalidFieldType
|
||||
} else if ret, err := p.parseFieldType(pkgName, t.Value); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return MapPrefix + ret, nil
|
||||
}
|
||||
case *ast.SelectorExpr: // <pkg_name>.<field_name>
|
||||
pkg, ok := t.X.(*ast.Ident)
|
||||
if !ok {
|
||||
@@ -388,4 +417,5 @@ const (
|
||||
JsonTypeString JsonType = "string"
|
||||
JsonTypeObject JsonType = "object"
|
||||
JsonTypeArray JsonType = "array"
|
||||
JsonTypeMap JsonType = "map"
|
||||
)
|
||||
|
||||
@@ -76,6 +76,7 @@ func getServerCommand() *cobra.Command {
|
||||
Debug: true,
|
||||
NativeIstio: true,
|
||||
HttpAddress: ":8888",
|
||||
CertHttpAddress: ":8889",
|
||||
GrpcAddress: ":15051",
|
||||
GrpcKeepAliveOptions: keepalive.DefaultOption(),
|
||||
XdsOptions: bootstrap.XdsOptions{
|
||||
@@ -117,6 +118,10 @@ func getServerCommand() *cobra.Command {
|
||||
serveCmd.PersistentFlags().Uint32Var(&serverArgs.GatewayHttpsPort, "gatewayHttpsPort", 443,
|
||||
"Https listening port of gateway pod")
|
||||
|
||||
serveCmd.PersistentFlags().BoolVar(&serverArgs.EnableAutomaticHttps, "enableAutomaticHttps", false, "if true, enables automatic https")
|
||||
serveCmd.PersistentFlags().StringVar(&serverArgs.AutomaticHttpsEmail, "automaticHttpsEmail", "", "email for automatic https")
|
||||
serveCmd.PersistentFlags().StringVar(&serverArgs.CertHttpAddress, "certHttpAddress", serverArgs.CertHttpAddress, "the cert http address")
|
||||
|
||||
loggingOptions.AttachCobraFlags(serveCmd)
|
||||
serverArgs.GrpcKeepAliveOptions.AttachCobraFlags(serveCmd)
|
||||
|
||||
|
||||
@@ -513,6 +513,7 @@ func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions
|
||||
var envoyFilters []config.Config
|
||||
mappings := map[string]*common.Rule{}
|
||||
|
||||
initHttp2RpcGlobalConfig := true
|
||||
for _, routes := range convertOptions.HTTPRoutes {
|
||||
for _, route := range routes {
|
||||
if strings.HasSuffix(route.HTTPRoute.Name, "app-root") {
|
||||
@@ -522,12 +523,13 @@ func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions
|
||||
http2rpc := route.WrapperConfig.AnnotationsConfig.Http2Rpc
|
||||
if http2rpc != nil {
|
||||
IngressLog.Infof("Found http2rpc for name %s", http2rpc.Name)
|
||||
envoyFilter, err := m.constructHttp2RpcEnvoyFilter(http2rpc, route, m.namespace)
|
||||
envoyFilter, err := m.constructHttp2RpcEnvoyFilter(http2rpc, route, m.namespace, initHttp2RpcGlobalConfig)
|
||||
if err != nil {
|
||||
IngressLog.Infof("Construct http2rpc EnvoyFilter error %v", err)
|
||||
} else {
|
||||
IngressLog.Infof("Append http2rpc EnvoyFilter for name %s", http2rpc.Name)
|
||||
envoyFilters = append(envoyFilters, *envoyFilter)
|
||||
initHttp2RpcGlobalConfig = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1143,7 +1145,7 @@ func (m *IngressConfig) applyCanaryIngresses(convertOptions *common.ConvertOptio
|
||||
}
|
||||
}
|
||||
|
||||
func (m *IngressConfig) constructHttp2RpcEnvoyFilter(http2rpcConfig *annotations.Http2RpcConfig, route *common.WrapperHTTPRoute, namespace string) (*config.Config, error) {
|
||||
func (m *IngressConfig) constructHttp2RpcEnvoyFilter(http2rpcConfig *annotations.Http2RpcConfig, route *common.WrapperHTTPRoute, namespace string, initHttp2RpcGlobalConfig bool) (*config.Config, error) {
|
||||
mappings := m.http2rpcs
|
||||
IngressLog.Infof("Found http2rpc mappings %v", mappings)
|
||||
if _, exist := mappings[http2rpcConfig.Name]; !exist {
|
||||
@@ -1163,75 +1165,39 @@ func (m *IngressConfig) constructHttp2RpcEnvoyFilter(http2rpcConfig *annotations
|
||||
if err != nil {
|
||||
return nil, errors.New(err.Error())
|
||||
}
|
||||
|
||||
return &config.Config{
|
||||
Meta: config.Meta{
|
||||
GroupVersionKind: gvk.EnvoyFilter,
|
||||
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, http2rpcConfig.Name),
|
||||
Namespace: namespace,
|
||||
configPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
|
||||
{
|
||||
ApplyTo: networking.EnvoyFilter_HTTP_ROUTE,
|
||||
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
|
||||
Context: networking.EnvoyFilter_GATEWAY,
|
||||
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{
|
||||
RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{
|
||||
Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{
|
||||
Route: &networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch{
|
||||
Name: httpRoute.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Patch: &networking.EnvoyFilter_Patch{
|
||||
Operation: networking.EnvoyFilter_Patch_MERGE,
|
||||
Value: typeStruct,
|
||||
},
|
||||
},
|
||||
Spec: &networking.EnvoyFilter{
|
||||
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
|
||||
{
|
||||
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
|
||||
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
|
||||
Context: networking.EnvoyFilter_GATEWAY,
|
||||
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
|
||||
Listener: &networking.EnvoyFilter_ListenerMatch{
|
||||
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
|
||||
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
|
||||
Name: "envoy.filters.network.http_connection_manager",
|
||||
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
|
||||
Name: "envoy.filters.http.router",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Patch: &networking.EnvoyFilter_Patch{
|
||||
Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE,
|
||||
Value: buildPatchStruct(`{
|
||||
"name":"envoy.filters.http.http_dubbo_transcoder",
|
||||
"typed_config":{
|
||||
"@type":"type.googleapis.com/udpa.type.v1.TypedStruct",
|
||||
"type_url":"type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder"
|
||||
}
|
||||
}`),
|
||||
{
|
||||
ApplyTo: networking.EnvoyFilter_CLUSTER,
|
||||
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
|
||||
Context: networking.EnvoyFilter_GATEWAY,
|
||||
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
|
||||
Cluster: &networking.EnvoyFilter_ClusterMatch{
|
||||
Service: httpRouteDestination.Destination.Host,
|
||||
},
|
||||
},
|
||||
{
|
||||
ApplyTo: networking.EnvoyFilter_HTTP_ROUTE,
|
||||
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
|
||||
Context: networking.EnvoyFilter_GATEWAY,
|
||||
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{
|
||||
RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{
|
||||
Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{
|
||||
Route: &networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch{
|
||||
Name: httpRoute.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Patch: &networking.EnvoyFilter_Patch{
|
||||
Operation: networking.EnvoyFilter_Patch_MERGE,
|
||||
Value: typeStruct,
|
||||
},
|
||||
},
|
||||
{
|
||||
ApplyTo: networking.EnvoyFilter_CLUSTER,
|
||||
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
|
||||
Context: networking.EnvoyFilter_GATEWAY,
|
||||
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
|
||||
Cluster: &networking.EnvoyFilter_ClusterMatch{
|
||||
Service: httpRouteDestination.Destination.Host,
|
||||
},
|
||||
},
|
||||
},
|
||||
Patch: &networking.EnvoyFilter_Patch{
|
||||
Operation: networking.EnvoyFilter_Patch_MERGE,
|
||||
Value: buildPatchStruct(`{
|
||||
},
|
||||
Patch: &networking.EnvoyFilter_Patch{
|
||||
Operation: networking.EnvoyFilter_Patch_MERGE,
|
||||
Value: buildPatchStruct(`{
|
||||
"upstream_config": {
|
||||
"name":"envoy.upstreams.http.dubbo_tcp",
|
||||
"typed_config":{
|
||||
@@ -1240,9 +1206,47 @@ func (m *IngressConfig) constructHttp2RpcEnvoyFilter(http2rpcConfig *annotations
|
||||
}
|
||||
}
|
||||
}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
if initHttp2RpcGlobalConfig {
|
||||
configPatches = append(configPatches, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
|
||||
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
|
||||
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
|
||||
Context: networking.EnvoyFilter_GATEWAY,
|
||||
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
|
||||
Listener: &networking.EnvoyFilter_ListenerMatch{
|
||||
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
|
||||
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
|
||||
Name: "envoy.filters.network.http_connection_manager",
|
||||
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
|
||||
Name: "envoy.filters.http.router",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Patch: &networking.EnvoyFilter_Patch{
|
||||
Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE,
|
||||
Value: buildPatchStruct(`{
|
||||
"name":"envoy.filters.http.http_dubbo_transcoder",
|
||||
"typed_config":{
|
||||
"@type":"type.googleapis.com/udpa.type.v1.TypedStruct",
|
||||
"type_url":"type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder"
|
||||
}
|
||||
}`),
|
||||
},
|
||||
})
|
||||
}
|
||||
return &config.Config{
|
||||
Meta: config.Meta{
|
||||
GroupVersionKind: gvk.EnvoyFilter,
|
||||
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, http2rpcConfig.Name),
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: &networking.EnvoyFilter{
|
||||
ConfigPatches: configPatches,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -636,6 +636,9 @@ func (g *GlobalOptionController) constructBufferLimit(downstream *Downstream) st
|
||||
|
||||
// constructRouteTimeout constructs the route timeout config.
|
||||
func (g *GlobalOptionController) constructRouteTimeout(downstream *Downstream) string {
|
||||
if downstream.RouteTimeout == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(`
|
||||
{
|
||||
"route": {
|
||||
|
||||
@@ -24,6 +24,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cert"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
@@ -85,6 +87,8 @@ type controller struct {
|
||||
secretController secret.SecretController
|
||||
|
||||
statusSyncer *statusSyncer
|
||||
|
||||
configMgr *cert.ConfigMgr
|
||||
}
|
||||
|
||||
// NewController creates a new Kubernetes controller
|
||||
@@ -103,6 +107,7 @@ func NewController(localKubeClient, client kubeclient.Client, options common.Opt
|
||||
IngressLog.Infof("Skipping IngressClass, resource not supported for cluster %s", options.ClusterId)
|
||||
}
|
||||
|
||||
configMgr, _ := cert.NewConfigMgr(options.SystemNamespace, client.Kube())
|
||||
c := &controller{
|
||||
options: options,
|
||||
queue: q,
|
||||
@@ -113,6 +118,7 @@ func NewController(localKubeClient, client kubeclient.Client, options common.Opt
|
||||
serviceInformer: serviceInformer.Informer(),
|
||||
serviceLister: serviceInformer.Lister(),
|
||||
secretController: secretController,
|
||||
configMgr: configMgr,
|
||||
}
|
||||
|
||||
handler := controllers.LatestVersionHandlerFuncs(controllers.EnqueueForSelf(q))
|
||||
@@ -371,7 +377,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
common.IncrementInvalidIngress(c.options.ClusterId, common.EmptyRule)
|
||||
return fmt.Errorf("invalid ingress rule %s:%s in cluster %s, either `defaultBackend` or `rules` must be specified", cfg.Namespace, cfg.Name, c.options.ClusterId)
|
||||
}
|
||||
|
||||
httpsCredentialConfig, _ := c.configMgr.GetConfigFromConfigmap()
|
||||
for _, rule := range ingressV1Beta.Rules {
|
||||
// Need create builder for every rule.
|
||||
domainBuilder := &common.IngressDomainBuilder{
|
||||
@@ -422,13 +428,19 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
|
||||
// Get tls secret matching the rule host
|
||||
secretName := extractTLSSecretName(rule.Host, ingressV1Beta.TLS)
|
||||
secretNamespace := cfg.Namespace
|
||||
// If there is no matching secret, try to get it from configmap.
|
||||
if secretName == "" && httpsCredentialConfig != nil {
|
||||
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
|
||||
secretNamespace = c.options.SystemNamespace
|
||||
}
|
||||
if secretName == "" {
|
||||
// There no matching secret, so just skip.
|
||||
continue
|
||||
}
|
||||
|
||||
domainBuilder.Protocol = common.HTTPS
|
||||
domainBuilder.SecretName = path.Join(c.options.ClusterId, cfg.Namespace, secretName)
|
||||
domainBuilder.SecretName = path.Join(c.options.ClusterId, secretNamespace, secretName)
|
||||
|
||||
// There is a matching secret and the gateway has already a tls secret.
|
||||
// We should report the duplicated tls secret event.
|
||||
@@ -450,7 +462,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
Hosts: []string{rule.Host},
|
||||
Tls: &networking.ServerTLSSettings{
|
||||
Mode: networking.ServerTLSSettings_SIMPLE,
|
||||
CredentialName: credentials.ToKubernetesIngressResource(c.options.RawClusterId, cfg.Namespace, secretName),
|
||||
CredentialName: credentials.ToKubernetesIngressResource(c.options.RawClusterId, secretNamespace, secretName),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -879,15 +891,28 @@ func (c *controller) storeBackendTrafficPolicy(wrapper *common.WrapperConfig, ba
|
||||
}
|
||||
if common.ValidateBackendResource(backend.Resource) && wrapper.AnnotationsConfig.Destination != nil {
|
||||
for _, dest := range wrapper.AnnotationsConfig.Destination.McpDestination {
|
||||
portNumber := dest.Destination.GetPort().GetNumber()
|
||||
serviceKey := common.ServiceKey{
|
||||
Namespace: "mcp",
|
||||
Name: dest.Destination.Host,
|
||||
Port: int32(portNumber),
|
||||
ServiceFQDN: dest.Destination.Host,
|
||||
}
|
||||
if _, exist := store[serviceKey]; !exist {
|
||||
store[serviceKey] = &common.WrapperTrafficPolicy{
|
||||
TrafficPolicy: &networking.TrafficPolicy{},
|
||||
WrapperConfig: wrapper,
|
||||
if serviceKey.Port != 0 {
|
||||
store[serviceKey] = &common.WrapperTrafficPolicy{
|
||||
PortTrafficPolicy: &networking.TrafficPolicy_PortTrafficPolicy{
|
||||
Port: &networking.PortSelector{
|
||||
Number: uint32(serviceKey.Port),
|
||||
},
|
||||
},
|
||||
WrapperConfig: wrapper,
|
||||
}
|
||||
} else {
|
||||
store[serviceKey] = &common.WrapperTrafficPolicy{
|
||||
TrafficPolicy: &networking.TrafficPolicy{},
|
||||
WrapperConfig: wrapper,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/pkg/cert"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
@@ -84,6 +85,8 @@ type controller struct {
|
||||
secretController secret.SecretController
|
||||
|
||||
statusSyncer *statusSyncer
|
||||
|
||||
configMgr *cert.ConfigMgr
|
||||
}
|
||||
|
||||
// NewController creates a new Kubernetes controller
|
||||
@@ -96,6 +99,7 @@ func NewController(localKubeClient, client kubeclient.Client, options common.Opt
|
||||
classes := client.KubeInformer().Networking().V1().IngressClasses()
|
||||
classes.Informer()
|
||||
|
||||
configMgr, _ := cert.NewConfigMgr(options.SystemNamespace, client.Kube())
|
||||
c := &controller{
|
||||
options: options,
|
||||
queue: q,
|
||||
@@ -106,6 +110,7 @@ func NewController(localKubeClient, client kubeclient.Client, options common.Opt
|
||||
serviceInformer: serviceInformer.Informer(),
|
||||
serviceLister: serviceInformer.Lister(),
|
||||
secretController: secretController,
|
||||
configMgr: configMgr,
|
||||
}
|
||||
|
||||
handler := controllers.LatestVersionHandlerFuncs(controllers.EnqueueForSelf(q))
|
||||
@@ -358,7 +363,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
return fmt.Errorf("invalid ingress rule %s:%s in cluster %s, either `defaultBackend` or `rules` must be specified", cfg.Namespace, cfg.Name, c.options.ClusterId)
|
||||
}
|
||||
|
||||
|
||||
httpsCredentialConfig, _ := c.configMgr.GetConfigFromConfigmap()
|
||||
for _, rule := range ingressV1.Rules {
|
||||
// Need create builder for every rule.
|
||||
domainBuilder := &common.IngressDomainBuilder{
|
||||
@@ -409,13 +414,19 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
|
||||
// Get tls secret matching the rule host
|
||||
secretName := extractTLSSecretName(rule.Host, ingressV1.TLS)
|
||||
secretNamespace := cfg.Namespace
|
||||
// If there is no matching secret, try to get it from configmap.
|
||||
if secretName == "" && httpsCredentialConfig != nil {
|
||||
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
|
||||
secretNamespace = c.options.SystemNamespace
|
||||
}
|
||||
if secretName == "" {
|
||||
// There no matching secret, so just skip.
|
||||
continue
|
||||
}
|
||||
|
||||
domainBuilder.Protocol = common.HTTPS
|
||||
domainBuilder.SecretName = path.Join(c.options.ClusterId, cfg.Namespace, secretName)
|
||||
domainBuilder.SecretName = path.Join(c.options.ClusterId, secretNamespace, secretName)
|
||||
|
||||
// There is a matching secret and the gateway has already a tls secret.
|
||||
// We should report the duplicated tls secret event.
|
||||
@@ -437,7 +448,7 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
Hosts: []string{rule.Host},
|
||||
Tls: &networking.ServerTLSSettings{
|
||||
Mode: networking.ServerTLSSettings_SIMPLE,
|
||||
CredentialName: credentials.ToKubernetesIngressResource(c.options.RawClusterId, cfg.Namespace, secretName),
|
||||
CredentialName: credentials.ToKubernetesIngressResource(c.options.RawClusterId, secretNamespace, secretName),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -880,15 +891,28 @@ func (c *controller) storeBackendTrafficPolicy(wrapper *common.WrapperConfig, ba
|
||||
}
|
||||
if common.ValidateBackendResource(backend.Resource) && wrapper.AnnotationsConfig.Destination != nil {
|
||||
for _, dest := range wrapper.AnnotationsConfig.Destination.McpDestination {
|
||||
portNumber := dest.Destination.GetPort().GetNumber()
|
||||
serviceKey := common.ServiceKey{
|
||||
Namespace: "mcp",
|
||||
Name: dest.Destination.Host,
|
||||
Port: int32(portNumber),
|
||||
ServiceFQDN: dest.Destination.Host,
|
||||
}
|
||||
if _, exist := store[serviceKey]; !exist {
|
||||
store[serviceKey] = &common.WrapperTrafficPolicy{
|
||||
TrafficPolicy: &networking.TrafficPolicy{},
|
||||
WrapperConfig: wrapper,
|
||||
if serviceKey.Port != 0 {
|
||||
store[serviceKey] = &common.WrapperTrafficPolicy{
|
||||
PortTrafficPolicy: &networking.TrafficPolicy_PortTrafficPolicy{
|
||||
Port: &networking.PortSelector{
|
||||
Number: uint32(serviceKey.Port),
|
||||
},
|
||||
},
|
||||
WrapperConfig: wrapper,
|
||||
}
|
||||
} else {
|
||||
store[serviceKey] = &common.WrapperTrafficPolicy{
|
||||
TrafficPolicy: &networking.TrafficPolicy{},
|
||||
WrapperConfig: wrapper,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ func (m *IngressTranslation) InitializeCluster(ingressController common.IngressC
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *IngressTranslation) GetIngressConfig() *ingressconfig.IngressConfig {
|
||||
return m.ingressConfig
|
||||
}
|
||||
|
||||
func (m *IngressTranslation) RegisterEventHandler(kind config.GroupVersionKind, f model.EventHandler) {
|
||||
m.ingressConfig.RegisterEventHandler(kind, f)
|
||||
if m.kingressConfig != nil {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
目前 Higress 提供了 c++ 和 golang 两种 Wasm 插件开发框架,支持 Wasm 插件路由&域名级匹配生效。
|
||||
|
||||
同时提供了多个内置插件,用户可以基于 Higress 提供的官方镜像仓库直接使用这些插件:
|
||||
同时提供了多个内置插件,用户可以基于 Higress 提供的官方镜像仓库直接使用这些插件(以 c++ 版本举例):
|
||||
|
||||
[basic-auth](./wasm-cpp/extensions/basic_auth):Basic Auth 认证鉴权
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
[request-block](./wasm-cpp/extensions/request_block):自定义请求屏蔽
|
||||
|
||||
使用方式具体可以参考此[文档](./wasm-go/README.md) 中相关说明。
|
||||
使用方式具体可以参考此 [wasm-cpp Plugin文档](./wasm-cpp/README.md) ,或 [wasm-go Plugin文档](./wasm-go/README.md) 中相关说明。
|
||||
|
||||
所有内置插件都已上传至 Higress 的官方镜像仓库:higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins
|
||||
|
||||
@@ -46,7 +46,7 @@ spec:
|
||||
|
||||
如果您想要为 Higress 贡献插件请参考下述说明。
|
||||
|
||||
根据你选择的开发语言,将插件代码放到 [wasm-cpp/extensions](./wasm-cpp/extensions) ,或者 [go-cpp/extensions](./wasm-go/extensions) 目录下。
|
||||
根据你选择的开发语言,将插件代码放到 [wasm-cpp/extensions](./wasm-cpp/extensions) ,或者 [wasm-go/extensions](./wasm-go/extensions) 目录下。
|
||||
|
||||
除了代码以外,需要额外提供一个 README.md 文件说明插件配置方式,以及 VERSION 文件用于记录插件版本,用作推送镜像时的 tag。
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
|
||||
load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary")
|
||||
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
|
||||
|
||||
wasm_cc_binary(
|
||||
proxy_wasm_cc_binary(
|
||||
name = "basic_auth.wasm",
|
||||
srcs = [
|
||||
"plugin.cc",
|
||||
@@ -28,7 +28,6 @@ wasm_cc_binary(
|
||||
"//common:crypto_util",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ FROM $BUILDER as builder
|
||||
ARG GOPROXY
|
||||
ENV GOPROXY=${GOPROXY}
|
||||
|
||||
ARG EXTRA_TAGS=""
|
||||
ENV EXTRA_TAGS=${EXTRA_TAGS}
|
||||
|
||||
ARG PLUGIN_NAME=hello-world
|
||||
|
||||
WORKDIR /workspace
|
||||
@@ -14,7 +17,7 @@ COPY . .
|
||||
WORKDIR /workspace/extensions/$PLUGIN_NAME
|
||||
|
||||
RUN go mod tidy
|
||||
RUN tinygo build -o /main.wasm -scheduler=none -gc=custom -tags='custommalloc nottinygc_finalizer' -target=wasi ./
|
||||
RUN tinygo build -o /main.wasm -scheduler=none -gc=custom -tags="custommalloc nottinygc_finalizer $EXTRA_TAGS" -target=wasi ./
|
||||
|
||||
FROM scratch as output
|
||||
|
||||
|
||||
19
plugins/wasm-go/extensions/ai-proxy/.gitignore
vendored
Normal file
19
plugins/wasm-go/extensions/ai-proxy/.gitignore
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# File generated by hgctl. Modify as required.
|
||||
|
||||
*
|
||||
|
||||
!/.gitignore
|
||||
|
||||
!*.go
|
||||
!go.sum
|
||||
!go.mod
|
||||
|
||||
!LICENSE
|
||||
!*.md
|
||||
!*.yaml
|
||||
!*.yml
|
||||
|
||||
!*/
|
||||
|
||||
/out
|
||||
/test
|
||||
609
plugins/wasm-go/extensions/ai-proxy/README.md
Normal file
609
plugins/wasm-go/extensions/ai-proxy/README.md
Normal file
@@ -0,0 +1,609 @@
|
||||
---
|
||||
title: AI 代理
|
||||
keywords: [ higress,ai,proxy,rag ]
|
||||
description: AI 代理插件配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
|
||||
`AI 代理`插件实现了基于 OpenAI API 契约的 AI 代理功能。目前支持 OpenAI、Azure OpenAI、月之暗面(Moonshot)和通义千问等 AI
|
||||
服务提供商。
|
||||
|
||||
## 配置字段
|
||||
|
||||
### 基本配置
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|------------|--------|------|-----|------------------|
|
||||
| `provider` | object | 必填 | - | 配置目标 AI 服务提供商的信息 |
|
||||
|
||||
`provider`的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|----------------|-----------------|------|-----|----------------------------------------------------------------------------------|
|
||||
| `type` | string | 必填 | - | AI 服务提供商名称。目前支持以下取值:openai, azure, moonshot, qwen |
|
||||
| `apiTokens` | array of string | 必填 | - | 用于在访问 AI 服务时进行认证的令牌。如果配置了多个 token,插件会在请求时随机进行选择。部分服务提供商只支持配置一个 token。 |
|
||||
| `timeout` | number | 非必填 | - | 访问 AI 服务的超时时间。单位为毫秒。默认值为 120000,即 2 分钟 |
|
||||
| `modelMapping` | map of string | 非必填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。<br/>可以使用 "*" 为键来配置通用兜底映射关系 |
|
||||
| `protocol` | string | 非必填 | - | 插件对外提供的 API 接口契约。目前支持以下取值:openai(默认值,使用 OpenAI 的接口契约)、original(使用目标服务提供商的原始接口契约) |
|
||||
| `context` | object | 非必填 | - | 配置 AI 对话上下文信息 |
|
||||
|
||||
`context`的配置字段说明如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|---------------|--------|------|-----|----------------------------------|
|
||||
| `fileUrl` | string | 必填 | - | 保存 AI 对话上下文的文件 URL。仅支持纯文本类型的文件内容 |
|
||||
| `serviceName` | string | 必填 | - | URL 所对应的 Higress 后端服务完整名称 |
|
||||
| `servicePort` | number | 必填 | - | URL 所对应的 Higress 后端服务访问端口 |
|
||||
|
||||
### 提供商特有配置
|
||||
|
||||
#### OpenAI
|
||||
|
||||
OpenAI 所对应的 `type` 为 `openai`。它并无特有的配置字段。
|
||||
|
||||
#### Azure OpenAI
|
||||
|
||||
Azure OpenAI 所对应的 `type` 为 `azure`。它特有的配置字段如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|-------------------|--------|------|-----|----------------------------------------------|
|
||||
| `azureServiceUrl` | string | 必填 | - | Azure OpenAI 服务的 URL,须包含 `api-version` 查询参数。 |
|
||||
|
||||
**注意:** Azure OpenAI 只支持配置一个 API Token。
|
||||
|
||||
#### 月之暗面(Moonshot)
|
||||
|
||||
月之暗面所对应的 `type` 为 `moonshot`。它特有的配置字段如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|------------------|--------|------|-----|-------------------------------------------------------------|
|
||||
| `moonshotFileId` | string | 非必填 | - | 通过文件接口上传至月之暗面的文件 ID,其内容将被用做 AI 对话的上下文。不可与 `context` 字段同时配置。 |
|
||||
|
||||
#### 通义千问(Qwen)
|
||||
|
||||
通义千问所对应的 `type` 为 `qwen`。它特有的配置字段如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|--------------------|-----------------|------|-----|------------------------------------------------------------------|
|
||||
| `qwenEnableSearch` | boolean | 非必填 | - | 是否启用通义千问内置的互联网搜索功能。 |
|
||||
| `qwenFileIds` | array of string | 非必填 | - | 通过文件接口上传至Dashscope的文件 ID,其内容将被用做 AI 对话的上下文。不可与 `context` 字段同时配置。 |
|
||||
|
||||
#### 百川智能 (Baichuan AI)
|
||||
|
||||
百川智能所对应的 `type` 为 `baichuan` 。它并无特有的配置字段。
|
||||
|
||||
#### 零一万物(Yi)
|
||||
|
||||
零一万物所对应的 `type` 为 `yi`。它并无特有的配置字段。
|
||||
|
||||
#### DeepSeek(DeepSeek)
|
||||
|
||||
DeepSeek所对应的 `type` 为 `deepseek`。它并无特有的配置字段。
|
||||
|
||||
#### Groq
|
||||
|
||||
Groq 所对应的 `type` 为 `groq`。它并无特有的配置字段。
|
||||
|
||||
#### Anthropic Claude
|
||||
|
||||
Anthropic Claude 所对应的 `type` 为 `claude`。它特有的配置字段如下:
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|-----------|--------|-----|-----|-------------------|
|
||||
| `version` | string | 必填 | - | Claude 服务的 API 版本 |
|
||||
|
||||
## 用法示例
|
||||
|
||||
### 使用 OpenAI 协议代理 Azure OpenAI 服务
|
||||
|
||||
使用最基本的 Azure OpenAI 服务,不配置任何上下文。
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: azure
|
||||
apiTokens:
|
||||
- "YOUR_AZURE_OPENAI_API_TOKEN"
|
||||
azureServiceUrl: "https://YOUR_RESOURCE_NAME.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT_NAME/chat/completions?api-version=2024-02-15-preview",
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-3",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你好,你是谁?"
|
||||
}
|
||||
],
|
||||
"temperature": 0.3
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"choices": [
|
||||
{
|
||||
"content_filter_results": {
|
||||
"hate": {
|
||||
"filtered": false,
|
||||
"severity": "safe"
|
||||
},
|
||||
"self_harm": {
|
||||
"filtered": false,
|
||||
"severity": "safe"
|
||||
},
|
||||
"sexual": {
|
||||
"filtered": false,
|
||||
"severity": "safe"
|
||||
},
|
||||
"violence": {
|
||||
"filtered": false,
|
||||
"severity": "safe"
|
||||
}
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
"index": 0,
|
||||
"logprobs": null,
|
||||
"message": {
|
||||
"content": "你好!我是一个AI助手,可以回答你的问题和提供帮助。有什么我可以帮到你的吗?",
|
||||
"role": "assistant"
|
||||
}
|
||||
}
|
||||
],
|
||||
"created": 1714807624,
|
||||
"id": "chatcmpl-abcdefg1234567890",
|
||||
"model": "gpt-35-turbo-16k",
|
||||
"object": "chat.completion",
|
||||
"prompt_filter_results": [
|
||||
{
|
||||
"prompt_index": 0,
|
||||
"content_filter_results": {
|
||||
"hate": {
|
||||
"filtered": false,
|
||||
"severity": "safe"
|
||||
},
|
||||
"self_harm": {
|
||||
"filtered": false,
|
||||
"severity": "safe"
|
||||
},
|
||||
"sexual": {
|
||||
"filtered": false,
|
||||
"severity": "safe"
|
||||
},
|
||||
"violence": {
|
||||
"filtered": false,
|
||||
"severity": "safe"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"system_fingerprint": null,
|
||||
"usage": {
|
||||
"completion_tokens": 40,
|
||||
"prompt_tokens": 15,
|
||||
"total_tokens": 55
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 OpenAI 协议代理通义千问服务
|
||||
|
||||
使用通义千问服务,并配置从 OpenAI 大模型到通义千问的模型映射关系。
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: qwen
|
||||
apiTokens:
|
||||
- "YOUR_QWEN_API_TOKEN"
|
||||
modelMapping:
|
||||
'gpt-3': "qwen-turbo"
|
||||
'gpt-35-turbo': "qwen-plus"
|
||||
'gpt-4-turbo': "qwen-max"
|
||||
'*': "qwen-turbo"
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-3",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你好,你是谁?"
|
||||
}
|
||||
],
|
||||
"temperature": 0.3
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "c2518bd3-0f46-97d1-be34-bb5777cb3108",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "我是通义千问,由阿里云开发的AI助手。我可以回答各种问题、提供信息和与用户进行对话。有什么我可以帮助你的吗?"
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"created": 1715175072,
|
||||
"model": "qwen-turbo",
|
||||
"object": "chat.completion",
|
||||
"usage": {
|
||||
"prompt_tokens": 24,
|
||||
"completion_tokens": 33,
|
||||
"total_tokens": 57
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用通义千问配合纯文本上下文信息
|
||||
|
||||
使用通义千问服务,同时配置纯文本上下文信息。
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: qwen
|
||||
apiTokens:
|
||||
- "YOUR_QWEN_API_TOKEN"
|
||||
modelMapping:
|
||||
"*": "qwen-turbo"
|
||||
context:
|
||||
- fileUrl: "http://file.default.svc.cluster.local/ai/context.txt",
|
||||
serviceName: "file.dns",
|
||||
servicePort: 80
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-3",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请概述文案内容"
|
||||
}
|
||||
],
|
||||
"temperature": 0.3
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "cmpl-77861a17681f4987ab8270dbf8001936",
|
||||
"object": "chat.completion",
|
||||
"created": 9756990,
|
||||
"model": "moonshot-v1-128k",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "这份文案是一份关于..."
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 20181,
|
||||
"completion_tokens": 439,
|
||||
"total_tokens": 20620
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用通义千问配合其原生的文件上下文
|
||||
|
||||
提前上传文件至通义千问,以文件内容作为上下文使用其 AI 服务。
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: qwen
|
||||
apiTokens:
|
||||
- "YOUR_QWEN_API_TOKEN"
|
||||
modelMapping:
|
||||
"*": "qwen-long" # 通义千问的文件上下文只能在 qwen-long 模型下使用
|
||||
qwenFileIds:
|
||||
- "file-fe-xxx"
|
||||
- "file-fe-yyy"
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-4-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请概述文案内容"
|
||||
}
|
||||
],
|
||||
"temperature": 0.3
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"output": {
|
||||
"choices": [
|
||||
{
|
||||
"finish_reason": "stop",
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "您上传了两个文件,`context.txt` 和 `context_2.txt`,它们似乎都包含了关于xxxx"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"usage": {
|
||||
"total_tokens": 2023,
|
||||
"output_tokens": 530,
|
||||
"input_tokens": 1493
|
||||
},
|
||||
"request_id": "187e99ba-5b64-9ffe-8f69-01dafbaf6ed7"
|
||||
}
|
||||
```
|
||||
|
||||
### 使用月之暗面配合其原生的文件上下文
|
||||
|
||||
提前上传文件至月之暗面,以文件内容作为上下文使用其 AI 服务。
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: moonshot
|
||||
apiTokens:
|
||||
- "YOUR_MOONSHOT_API_TOKEN"
|
||||
moonshotFileId: "YOUR_MOONSHOT_FILE_ID",
|
||||
modelMapping:
|
||||
'*': "moonshot-v1-32k"
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gpt-4-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请概述文案内容"
|
||||
}
|
||||
],
|
||||
"temperature": 0.3
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "cmpl-e5ca873642ca4f5d8b178c1742f9a8e8",
|
||||
"object": "chat.completion",
|
||||
"created": 1872961,
|
||||
"model": "moonshot-v1-128k",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "文案内容是关于一个名为“xxxx”的支付平台..."
|
||||
},
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 11,
|
||||
"completion_tokens": 498,
|
||||
"total_tokens": 509
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 OpenAI 协议代理 Groq 服务
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: groq
|
||||
apiTokens:
|
||||
- "YOUR_GROQ_API_TOKEN"
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "llama3-8b-8192",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你好,你是谁?"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "chatcmpl-26733989-6c52-4056-b7a9-5da791bd7102",
|
||||
"object": "chat.completion",
|
||||
"created": 1715917967,
|
||||
"model": "llama3-8b-8192",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "😊 Ni Hao! (That's \"hello\" in Chinese!)\n\nI am LLaMA, an AI assistant developed by Meta AI that can understand and respond to human input in a conversational manner. I'm not a human, but a computer program designed to simulate conversations and answer questions to the best of my ability. I'm happy to chat with you in Chinese or help with any questions or topics you'd like to discuss! 😊"
|
||||
},
|
||||
"logprobs": null,
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 16,
|
||||
"prompt_time": 0.005,
|
||||
"completion_tokens": 89,
|
||||
"completion_time": 0.104,
|
||||
"total_tokens": 105,
|
||||
"total_time": 0.109
|
||||
},
|
||||
"system_fingerprint": "fp_dadc9d6142",
|
||||
"x_groq": {
|
||||
"id": "req_01hy2awmcxfpwbq56qh6svm7qz"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 OpenAI 协议代理 Claude 服务
|
||||
|
||||
**配置信息**
|
||||
|
||||
```yaml
|
||||
provider:
|
||||
type: claude
|
||||
apiTokens:
|
||||
- "YOUR_CLAUDE_API_TOKEN"
|
||||
```
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "claude-3-opus-20240229",
|
||||
"max_tokens": 1024,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你好,你是谁?"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "msg_01K8iLH18FGN7Xd9deurwtoD",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "claude-3-opus-20240229",
|
||||
"stop_sequence": null,
|
||||
"usage": {
|
||||
"input_tokens": 16,
|
||||
"output_tokens": 141
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "你好!我是Claude,一个由Anthropic公司开发的人工智能助手。我的任务是尽我所能帮助人类,比如回答问题,提供建议和意见,协助完成任务等。我掌握了很多知识,也具备一定的分析和推理能力,但我不是人类,也没有实体的身体。很高兴认识你!如果有什么需要帮助的地方,欢迎随时告诉我。"
|
||||
}
|
||||
],
|
||||
"stop_reason": "end_turn"
|
||||
}
|
||||
```
|
||||
|
||||
## 完整配置示例
|
||||
|
||||
以下以使用 OpenAI 协议代理 Groq 服务为例,展示完整的插件配置示例。
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: ai-proxy-groq
|
||||
namespace: higress-system
|
||||
spec:
|
||||
matchRules:
|
||||
- config:
|
||||
provider:
|
||||
type: groq
|
||||
apiTokens:
|
||||
- "YOUR_API_TOKEN"
|
||||
ingress:
|
||||
- groq
|
||||
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/ai-proxy:1.0.0
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/backend-protocol: HTTPS
|
||||
higress.io/destination: groq.dns
|
||||
higress.io/proxy-ssl-name: api.groq.com
|
||||
higress.io/proxy-ssl-server-name: "on"
|
||||
labels:
|
||||
higress.io/resource-definer: higress
|
||||
name: groq
|
||||
namespace: higress-system
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: <YOUR-DOMAIN>
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
resource:
|
||||
apiGroup: networking.higress.io
|
||||
kind: McpBridge
|
||||
name: default
|
||||
path: /
|
||||
pathType: Prefix
|
||||
---
|
||||
apiVersion: networking.higress.io/v1
|
||||
kind: McpBridge
|
||||
metadata:
|
||||
name: default
|
||||
namespace: higress-system
|
||||
spec:
|
||||
registries:
|
||||
- domain: api.groq.com
|
||||
name: groq
|
||||
port: 443
|
||||
type: dns
|
||||
```
|
||||
|
||||
访问示例:
|
||||
|
||||
```bash
|
||||
curl "http://<YOUR-DOMAIN>/v1/chat/completions" -H "Content-Type: application/json" -d '{
|
||||
"model": "llama3-8b-8192",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "你好,你是谁?"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
52
plugins/wasm-go/extensions/ai-proxy/config/config.go
Normal file
52
plugins/wasm-go/extensions/ai-proxy/config/config.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/provider"
|
||||
)
|
||||
|
||||
// @Name ai-proxy
|
||||
// @Category custom
|
||||
// @Phase UNSPECIFIED_PHASE
|
||||
// @Priority 0
|
||||
// @Title zh-CN AI代理
|
||||
// @Description zh-CN 通过AI助手提供智能对话服务
|
||||
// @IconUrl https://img.alicdn.com/imgextra/i1/O1CN018iKKih1iVx287RltL_!!6000000004419-2-tps-42-42.png
|
||||
// @Version 0.1.0
|
||||
//
|
||||
// @Contact.name CH3CHO
|
||||
// @Contact.url https://github.com/CH3CHO
|
||||
// @Contact.email ch3cho@qq.com
|
||||
//
|
||||
// @Example
|
||||
// { "provider": { "type": "qwen", "apiToken": "YOUR_DASHSCOPE_API_TOKEN", "modelMapping": { "*": "qwen-turbo" } } }
|
||||
// @End
|
||||
type PluginConfig struct {
|
||||
// @Title zh-CN AI服务提供商配置
|
||||
// @Description zh-CN AI服务提供商配置,包含API接口、模型和知识库文件等信息
|
||||
providerConfig provider.ProviderConfig `required:"true" yaml:"provider"`
|
||||
|
||||
provider provider.Provider `yaml:"-"`
|
||||
}
|
||||
|
||||
func (c *PluginConfig) FromJson(json gjson.Result) {
|
||||
c.providerConfig.FromJson(json.Get("provider"))
|
||||
}
|
||||
|
||||
func (c *PluginConfig) Validate() error {
|
||||
if err := c.providerConfig.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *PluginConfig) Complete() error {
|
||||
var err error
|
||||
c.provider, err = provider.CreateProvider(c.providerConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *PluginConfig) GetProvider() provider.Provider {
|
||||
return c.provider
|
||||
}
|
||||
110
plugins/wasm-go/extensions/ai-proxy/envoy.yaml
Normal file
110
plugins/wasm-go/extensions/ai-proxy/envoy.yaml
Normal file
@@ -0,0 +1,110 @@
|
||||
# File generated by hgctl. Modify as required.
|
||||
|
||||
admin:
|
||||
address:
|
||||
socket_address:
|
||||
protocol: TCP
|
||||
address: 0.0.0.0
|
||||
port_value: 9901
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: listener_0
|
||||
address:
|
||||
socket_address:
|
||||
protocol: TCP
|
||||
address: 0.0.0.0
|
||||
port_value: 10000
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
scheme_header_transformation:
|
||||
scheme_to_overwrite: https
|
||||
stat_prefix: ingress_http
|
||||
# Output envoy logs to stdout
|
||||
access_log:
|
||||
- name: envoy.access_loggers.stdout
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
|
||||
# Modify as required
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains: [ "*" ]
|
||||
routes:
|
||||
- match:
|
||||
prefix: "/"
|
||||
route:
|
||||
cluster: moonshot
|
||||
timeout: 300s
|
||||
http_filters:
|
||||
- name: wasmtest
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
|
||||
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
|
||||
value:
|
||||
config:
|
||||
name: wasmtest
|
||||
vm_config:
|
||||
runtime: envoy.wasm.runtime.v8
|
||||
code:
|
||||
local:
|
||||
filename: /etc/envoy/plugin.wasm
|
||||
configuration:
|
||||
"@type": "type.googleapis.com/google.protobuf.StringValue"
|
||||
value: |
|
||||
{
|
||||
"provider": {
|
||||
"type": "moonshot",
|
||||
"domain": "api.moonshot.cn",
|
||||
"apiTokens": [
|
||||
"****",
|
||||
"****"
|
||||
],
|
||||
"timeout": 1200000,
|
||||
"modelMapping": {
|
||||
"gpt-3": "moonshot-v1-8k",
|
||||
"gpt-35-turbo": "moonshot-v1-32k",
|
||||
"gpt-4-turbo": "moonshot-v1-128k",
|
||||
"*": "moonshot-v1-8k"
|
||||
},
|
||||
}
|
||||
}
|
||||
- name: envoy.filters.http.router
|
||||
clusters:
|
||||
- name: httpbin
|
||||
connect_timeout: 30s
|
||||
type: LOGICAL_DNS
|
||||
# Comment out the following line to test on v6 networks
|
||||
dns_lookup_family: V4_ONLY
|
||||
lb_policy: ROUND_ROBIN
|
||||
load_assignment:
|
||||
cluster_name: httpbin
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: httpbin
|
||||
port_value: 80
|
||||
- name: moonshot
|
||||
connect_timeout: 30s
|
||||
type: LOGICAL_DNS
|
||||
dns_lookup_family: V4_ONLY
|
||||
lb_policy: ROUND_ROBIN
|
||||
load_assignment:
|
||||
cluster_name: moonshot
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: api.moonshot.cn
|
||||
port_value: 443
|
||||
transport_socket:
|
||||
name: envoy.transport_sockets.tls
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
|
||||
"sni": "api.moonshot.cn"
|
||||
26
plugins/wasm-go/extensions/ai-proxy/go.mod
Normal file
26
plugins/wasm-go/extensions/ai-proxy/go.mod
Normal file
@@ -0,0 +1,26 @@
|
||||
// File generated by hgctl. Modify as required.
|
||||
|
||||
module github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy
|
||||
|
||||
go 1.19
|
||||
|
||||
replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
|
||||
github.com/magefile/mage v1.14.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/tidwall/resp v0.1.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
26
plugins/wasm-go/extensions/ai-proxy/go.sum
Normal file
26
plugins/wasm-go/extensions/ai-proxy/go.sum
Normal file
@@ -0,0 +1,26 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
|
||||
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
212
plugins/wasm-go/extensions/ai-proxy/main.go
Normal file
212
plugins/wasm-go/extensions/ai-proxy/main.go
Normal file
@@ -0,0 +1,212 @@
|
||||
// File generated by hgctl. Modify as required.
|
||||
// See: https://higress.io/zh-cn/docs/user/wasm-go#2-%E7%BC%96%E5%86%99-maingo-%E6%96%87%E4%BB%B6
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/config"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/provider"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
pluginName = "ai-proxy"
|
||||
|
||||
ctxKeyApiName = "apiKey"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wrapper.SetCtx(
|
||||
pluginName,
|
||||
wrapper.ParseConfigBy(parseConfig),
|
||||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeader),
|
||||
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
|
||||
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
|
||||
wrapper.ProcessStreamingResponseBodyBy(onStreamingResponseBody),
|
||||
wrapper.ProcessResponseBodyBy(onHttpResponseBody),
|
||||
)
|
||||
}
|
||||
|
||||
func parseConfig(json gjson.Result, pluginConfig *config.PluginConfig, log wrapper.Log) error {
|
||||
//log.Debugf("loading config: %s", json.String())
|
||||
|
||||
pluginConfig.FromJson(json)
|
||||
if err := pluginConfig.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pluginConfig.Complete(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func onHttpRequestHeader(ctx wrapper.HttpContext, pluginConfig config.PluginConfig, log wrapper.Log) types.Action {
|
||||
activeProvider := pluginConfig.GetProvider()
|
||||
|
||||
if activeProvider == nil {
|
||||
log.Debugf("[onHttpRequestHeader] no active provider, skip processing")
|
||||
ctx.DontReadRequestBody()
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
log.Debugf("[onHttpRequestHeader] provider=%s", activeProvider.GetProviderType())
|
||||
|
||||
rawPath := ctx.Path()
|
||||
path, _ := url.Parse(rawPath)
|
||||
apiName := getApiName(path.Path)
|
||||
if apiName == "" {
|
||||
log.Debugf("[onHttpRequestHeader] unsupported path: %s", path.Path)
|
||||
_ = util.SendResponse(404, util.MimeTypeTextPlain, "API not found: "+path.Path)
|
||||
return types.ActionContinue
|
||||
}
|
||||
ctx.SetContext(ctxKeyApiName, apiName)
|
||||
|
||||
if handler, ok := activeProvider.(provider.RequestHeadersHandler); ok {
|
||||
// Disable the route re-calculation since the plugin may modify some headers related to the chosen route.
|
||||
ctx.DisableReroute()
|
||||
|
||||
action, err := handler.OnRequestHeaders(ctx, apiName, log)
|
||||
if err == nil {
|
||||
return action
|
||||
}
|
||||
_ = util.SendResponse(404, util.MimeTypeTextPlain, fmt.Sprintf("failed to process request headers: %v", err))
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
if _, needHandleBody := activeProvider.(provider.RequestBodyHandler); needHandleBody {
|
||||
ctx.DontReadRequestBody()
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpRequestBody(ctx wrapper.HttpContext, pluginConfig config.PluginConfig, body []byte, log wrapper.Log) types.Action {
|
||||
activeProvider := pluginConfig.GetProvider()
|
||||
|
||||
if activeProvider == nil {
|
||||
log.Debugf("[onHttpRequestBody] no active provider, skip processing")
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
log.Debugf("[onHttpRequestBody] provider=%s", activeProvider.GetProviderType())
|
||||
|
||||
if handler, ok := activeProvider.(provider.RequestBodyHandler); ok {
|
||||
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
|
||||
action, err := handler.OnRequestBody(ctx, apiName, body, log)
|
||||
if err == nil {
|
||||
return action
|
||||
}
|
||||
_ = util.SendResponse(404, util.MimeTypeTextPlain, fmt.Sprintf("failed to process request body: %v", err))
|
||||
return types.ActionContinue
|
||||
}
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpResponseHeaders(ctx wrapper.HttpContext, pluginConfig config.PluginConfig, log wrapper.Log) types.Action {
|
||||
activeProvider := pluginConfig.GetProvider()
|
||||
|
||||
if activeProvider == nil {
|
||||
log.Debugf("[onHttpResponseHeaders] no active provider, skip processing")
|
||||
ctx.DontReadResponseBody()
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
log.Debugf("[onHttpResponseHeaders] provider=%s", activeProvider.GetProviderType())
|
||||
|
||||
status, err := proxywasm.GetHttpResponseHeader(":status")
|
||||
if err != nil || status != "200" {
|
||||
if err != nil {
|
||||
log.Errorf("unable to load :status header from response: %v", err)
|
||||
}
|
||||
ctx.DontReadResponseBody()
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
contentType, err := proxywasm.GetHttpResponseHeader("Content-Type")
|
||||
if err != nil || !strings.HasPrefix(contentType, "text/event-stream") {
|
||||
if err != nil {
|
||||
log.Errorf("unable to load content-type header from response: %v", err)
|
||||
}
|
||||
ctx.BufferResponseBody()
|
||||
}
|
||||
|
||||
if handler, ok := activeProvider.(provider.ResponseHeadersHandler); ok {
|
||||
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
|
||||
action, err := handler.OnResponseHeaders(ctx, apiName, log)
|
||||
if err == nil {
|
||||
return action
|
||||
}
|
||||
_ = util.SendResponse(404, util.MimeTypeTextPlain, fmt.Sprintf("failed to process response headers: %v", err))
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
_, needHandleBody := activeProvider.(provider.ResponseBodyHandler)
|
||||
_, needHandleStreamingBody := activeProvider.(provider.StreamingResponseBodyHandler)
|
||||
if !needHandleBody && !needHandleStreamingBody {
|
||||
ctx.DontReadResponseBody()
|
||||
} else if !needHandleStreamingBody {
|
||||
ctx.BufferResponseBody()
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onStreamingResponseBody(ctx wrapper.HttpContext, pluginConfig config.PluginConfig, chunk []byte, isLastChunk bool, log wrapper.Log) []byte {
|
||||
activeProvider := pluginConfig.GetProvider()
|
||||
|
||||
if activeProvider == nil {
|
||||
log.Debugf("[onStreamingResponseBody] no active provider, skip processing")
|
||||
return chunk
|
||||
}
|
||||
|
||||
log.Debugf("[onStreamingResponseBody] provider=%s", activeProvider.GetProviderType())
|
||||
log.Debugf("isLastChunk=%v chunk: %s", isLastChunk, string(chunk))
|
||||
|
||||
if handler, ok := activeProvider.(provider.StreamingResponseBodyHandler); ok {
|
||||
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
|
||||
modifiedChunk, err := handler.OnStreamingResponseBody(ctx, apiName, chunk, isLastChunk, log)
|
||||
if err == nil && modifiedChunk != nil {
|
||||
return modifiedChunk
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
func onHttpResponseBody(ctx wrapper.HttpContext, pluginConfig config.PluginConfig, body []byte, log wrapper.Log) types.Action {
|
||||
activeProvider := pluginConfig.GetProvider()
|
||||
|
||||
if activeProvider == nil {
|
||||
log.Debugf("[onHttpResponseBody] no active provider, skip processing")
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
log.Debugf("[onHttpResponseBody] provider=%s", activeProvider.GetProviderType())
|
||||
//log.Debugf("response body: %s", string(body))
|
||||
|
||||
if handler, ok := activeProvider.(provider.ResponseBodyHandler); ok {
|
||||
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
|
||||
action, err := handler.OnResponseBody(ctx, apiName, body, log)
|
||||
if err == nil {
|
||||
return action
|
||||
}
|
||||
_ = util.SendResponse(404, util.MimeTypeTextPlain, fmt.Sprintf("failed to process response body: %v", err))
|
||||
return types.ActionContinue
|
||||
}
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func getApiName(path string) provider.ApiName {
|
||||
if strings.HasSuffix(path, "/v1/chat/completions") {
|
||||
return provider.ApiNameChatCompletion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
52
plugins/wasm-go/extensions/ai-proxy/option.yaml
Normal file
52
plugins/wasm-go/extensions/ai-proxy/option.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
# File generated by hgctl. Modify as required.
|
||||
|
||||
version: 1.0.0
|
||||
|
||||
build:
|
||||
# The official builder image version
|
||||
builder:
|
||||
go: 1.19
|
||||
tinygo: 0.28.1
|
||||
oras: 1.0.0
|
||||
# The WASM plugin project directory
|
||||
input: ./
|
||||
# The output of the build products
|
||||
output:
|
||||
# Choose between 'files' and 'image'
|
||||
type: files
|
||||
# Destination address: when type=files, specify the local directory path, e.g., './out' or
|
||||
# type=image, specify the remote docker repository, e.g., 'docker.io/<your_username>/<your_image>'
|
||||
dest: ./out
|
||||
# The authentication configuration for pushing image to the docker repository
|
||||
docker-auth: ~/.docker/config.json
|
||||
# The directory for the WASM plugin configuration structure
|
||||
model-dir: ./
|
||||
# The WASM plugin configuration structure name
|
||||
model: config.PluginConfig
|
||||
# Enable debug mode
|
||||
debug: false
|
||||
|
||||
test:
|
||||
# Test environment name, that is a docker compose project name
|
||||
name: wasm-test
|
||||
# The output path to build products, that is the source of test configuration parameters
|
||||
from-path: ./out
|
||||
# The test configuration source
|
||||
test-path: ./test
|
||||
# Docker compose configuration, which is empty, looks for the following files from 'test-path':
|
||||
# compose.yaml, compose.yml, docker-compose.yml, docker-compose.yaml
|
||||
compose-file:
|
||||
# Detached mode: Run containers in the background
|
||||
detach: false
|
||||
|
||||
install:
|
||||
# The namespace of the installation
|
||||
namespace: higress-system
|
||||
# Use to validate WASM plugin configuration when install by yaml
|
||||
spec-yaml: ./out/spec.yaml
|
||||
# Installation source. Choose between 'from-yaml' and 'from-go-project'
|
||||
from-yaml: ./test/plugin-conf.yaml
|
||||
# If 'from-go-src' is non-empty, the output type of the build option must be 'image'
|
||||
from-go-src:
|
||||
# Enable debug mode
|
||||
debug: false
|
||||
99
plugins/wasm-go/extensions/ai-proxy/provider/azure.go
Normal file
99
plugins/wasm-go/extensions/ai-proxy/provider/azure.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
// azureProvider is the provider for Azure OpenAI service.
|
||||
|
||||
type azureProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *azureProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
if config.azureServiceUrl == "" {
|
||||
return errors.New("missing azureServiceUrl in provider config")
|
||||
}
|
||||
if _, err := url.Parse(config.azureServiceUrl); err != nil {
|
||||
return fmt.Errorf("invalid azureServiceUrl: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *azureProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
var serviceUrl *url.URL
|
||||
if u, err := url.Parse(config.azureServiceUrl); err != nil {
|
||||
return nil, fmt.Errorf("invalid azureServiceUrl: %w", err)
|
||||
} else {
|
||||
serviceUrl = u
|
||||
}
|
||||
return &azureProvider{
|
||||
config: config,
|
||||
serviceUrl: serviceUrl,
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type azureProvider struct {
|
||||
config ProviderConfig
|
||||
|
||||
contextCache *contextCache
|
||||
serviceUrl *url.URL
|
||||
}
|
||||
|
||||
func (m *azureProvider) GetProviderType() string {
|
||||
return providerTypeAzure
|
||||
}
|
||||
|
||||
func (m *azureProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(m.serviceUrl.RequestURI())
|
||||
_ = util.OverwriteRequestHost(m.serviceUrl.Host)
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("api-key", m.config.apiTokens[0])
|
||||
|
||||
if m.contextCache == nil {
|
||||
ctx.DontReadRequestBody()
|
||||
} else {
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
}
|
||||
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *azureProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
if m.contextCache == nil {
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
insertContextMessage(request, content)
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
87
plugins/wasm-go/extensions/ai-proxy/provider/baichuan.go
Normal file
87
plugins/wasm-go/extensions/ai-proxy/provider/baichuan.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
// baichuanProvider is the provider for baichuan Ai service.
|
||||
|
||||
const (
|
||||
baichuanDomain = "api.baichuan-ai.com"
|
||||
baichuanChatCompletionPath = "/v1/chat/completions"
|
||||
)
|
||||
|
||||
type baichuanProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *baichuanProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *baichuanProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &baichuanProvider{
|
||||
config: config,
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type baichuanProvider struct {
|
||||
config ProviderConfig
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *baichuanProvider) GetProviderType() string {
|
||||
return providerTypeBaichuan
|
||||
}
|
||||
|
||||
func (m *baichuanProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(baichuanChatCompletionPath)
|
||||
_ = util.OverwriteRequestHost(baichuanDomain)
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken())
|
||||
|
||||
if m.contextCache == nil {
|
||||
ctx.DontReadRequestBody()
|
||||
} else {
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
}
|
||||
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *baichuanProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
if m.contextCache == nil {
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
insertContextMessage(request, content)
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
17
plugins/wasm-go/extensions/ai-proxy/provider/cluster.go
Normal file
17
plugins/wasm-go/extensions/ai-proxy/provider/cluster.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package provider
|
||||
|
||||
import "fmt"
|
||||
|
||||
type plainCluster struct {
|
||||
serviceName string
|
||||
servicePort int64
|
||||
hostName string
|
||||
}
|
||||
|
||||
func (c plainCluster) ClusterName() string {
|
||||
return fmt.Sprintf("outbound|%d||%s", c.servicePort, c.serviceName)
|
||||
}
|
||||
|
||||
func (c plainCluster) HostName() string {
|
||||
return c.hostName
|
||||
}
|
||||
100
plugins/wasm-go/extensions/ai-proxy/provider/context.go
Normal file
100
plugins/wasm-go/extensions/ai-proxy/provider/context.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type ContextConfig struct {
|
||||
// @Title zh-CN 文件URL
|
||||
// @Description zh-CN 用于获取对话上下文的文件的URL。目前仅支持HTTP和HTTPS协议,纯文本格式文件
|
||||
fileUrl string `required:"true" yaml:"url" json:"url"`
|
||||
// @Title zh-CN 上游服务名称
|
||||
// @Description zh-CN 文件服务所对应的网关内上游服务名称
|
||||
serviceName string `required:"true" yaml:"serviceName" json:"serviceName"`
|
||||
// @Title zh-CN 上游服务端口
|
||||
// @Description zh-CN 文件服务所对应的网关内上游服务名称
|
||||
servicePort int64 `required:"true" yaml:"servicePort" json:"servicePort"`
|
||||
|
||||
fileUrlObj *url.URL `yaml:"-"`
|
||||
}
|
||||
|
||||
func (c *ContextConfig) FromJson(json gjson.Result) {
|
||||
c.fileUrl = json.Get("fileUrl").String()
|
||||
c.serviceName = json.Get("serviceName").String()
|
||||
c.servicePort = json.Get("servicePort").Int()
|
||||
}
|
||||
|
||||
func (c *ContextConfig) Validate() error {
|
||||
if c.fileUrl == "" {
|
||||
return errors.New("missing fileUrl in context config")
|
||||
}
|
||||
if fileUrlObj, err := url.Parse(c.fileUrl); err != nil {
|
||||
return fmt.Errorf("invalid fileUrl in context config: %v", err)
|
||||
} else {
|
||||
c.fileUrlObj = fileUrlObj
|
||||
}
|
||||
if c.serviceName == "" {
|
||||
return errors.New("missing serviceName in context config")
|
||||
}
|
||||
if c.servicePort == 0 {
|
||||
return errors.New("missing servicePort in context config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type contextCache struct {
|
||||
client wrapper.HttpClient
|
||||
fileUrl *url.URL
|
||||
timeout uint32
|
||||
|
||||
loaded bool
|
||||
content string
|
||||
}
|
||||
|
||||
func (c *contextCache) GetContent(callback func(string, error), log wrapper.Log) error {
|
||||
if callback == nil {
|
||||
return errors.New("callback is nil")
|
||||
}
|
||||
|
||||
if c.loaded {
|
||||
log.Debugf("context file loaded from cache")
|
||||
callback(c.content, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("loading context file from %s", c.fileUrl.String())
|
||||
return c.client.Get(c.fileUrl.Path, nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
if statusCode != http.StatusOK {
|
||||
callback("", fmt.Errorf("failed to load context file, status: %d", statusCode))
|
||||
return
|
||||
}
|
||||
c.content = string(responseBody)
|
||||
c.loaded = true
|
||||
log.Debugf("content: %s", c.content)
|
||||
callback(c.content, nil)
|
||||
}, c.timeout)
|
||||
}
|
||||
|
||||
func createContextCache(providerConfig *ProviderConfig) *contextCache {
|
||||
contextConfig := providerConfig.context
|
||||
if contextConfig == nil {
|
||||
return nil
|
||||
}
|
||||
fileUrlObj, _ := url.Parse(contextConfig.fileUrl)
|
||||
cluster := plainCluster{
|
||||
serviceName: contextConfig.serviceName,
|
||||
servicePort: contextConfig.servicePort,
|
||||
hostName: fileUrlObj.Host,
|
||||
}
|
||||
return &contextCache{
|
||||
client: wrapper.NewClusterClient(cluster),
|
||||
fileUrl: fileUrlObj,
|
||||
timeout: providerConfig.timeout,
|
||||
}
|
||||
}
|
||||
87
plugins/wasm-go/extensions/ai-proxy/provider/deepseek.go
Normal file
87
plugins/wasm-go/extensions/ai-proxy/provider/deepseek.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
// deepseekProvider is the provider for deepseek Ai service.
|
||||
|
||||
const (
|
||||
deepseekDomain = "api.deepseek.com"
|
||||
deepseekChatCompletionPath = "/v1/chat/completions"
|
||||
)
|
||||
|
||||
type deepseekProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *deepseekProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *deepseekProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &deepseekProvider{
|
||||
config: config,
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type deepseekProvider struct {
|
||||
config ProviderConfig
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *deepseekProvider) GetProviderType() string {
|
||||
return providerTypeDeepSeek
|
||||
}
|
||||
|
||||
func (m *deepseekProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(deepseekChatCompletionPath)
|
||||
_ = util.OverwriteRequestHost(deepseekDomain)
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken())
|
||||
|
||||
if m.contextCache == nil {
|
||||
ctx.DontReadRequestBody()
|
||||
} else {
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
}
|
||||
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *deepseekProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
if m.contextCache == nil {
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
insertContextMessage(request, content)
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
85
plugins/wasm-go/extensions/ai-proxy/provider/groq.go
Normal file
85
plugins/wasm-go/extensions/ai-proxy/provider/groq.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
// groqProvider is the provider for Groq service.
|
||||
const (
|
||||
groqDomain = "api.groq.com"
|
||||
groqChatCompletionPath = "/openai/v1/chat/completions"
|
||||
)
|
||||
|
||||
type groqProviderInitializer struct{}
|
||||
|
||||
func (m *groqProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *groqProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &groqProvider{
|
||||
config: config,
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type groqProvider struct {
|
||||
config ProviderConfig
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *groqProvider) GetProviderType() string {
|
||||
return providerTypeGroq
|
||||
}
|
||||
|
||||
func (m *groqProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(groqChatCompletionPath)
|
||||
_ = util.OverwriteRequestHost(groqDomain)
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken())
|
||||
|
||||
if m.contextCache == nil {
|
||||
ctx.DontReadRequestBody()
|
||||
} else {
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
}
|
||||
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *groqProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
if m.contextCache == nil {
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
insertContextMessage(request, content)
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
140
plugins/wasm-go/extensions/ai-proxy/provider/model.go
Normal file
140
plugins/wasm-go/extensions/ai-proxy/provider/model.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package provider
|
||||
|
||||
import "strings"
|
||||
|
||||
const (
|
||||
streamEventIdItemKey = "id:"
|
||||
streamEventNameItemKey = "event:"
|
||||
streamBuiltInItemKey = ":"
|
||||
streamHttpStatusValuePrefix = "HTTP_STATUS/"
|
||||
streamDataItemKey = "data:"
|
||||
streamEndDataValue = "[DONE]"
|
||||
|
||||
eventResult = "result"
|
||||
|
||||
httpStatus200 = "200"
|
||||
)
|
||||
|
||||
type chatCompletionRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []chatMessage `json:"messages"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
|
||||
N int `json:"n,omitempty"`
|
||||
PresencePenalty float64 `json:"presence_penalty,omitempty"`
|
||||
Seed int `json:"seed,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
StreamOptions *streamOptions `json:"stream_options,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
Tools []tool `json:"tools,omitempty"`
|
||||
ToolChoice *toolChoice `json:"tool_choice,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
type streamOptions struct {
|
||||
IncludeUsage bool `json:"include_usage,omitempty"`
|
||||
}
|
||||
|
||||
type tool struct {
|
||||
Type string `json:"type"`
|
||||
Function function `json:"function"`
|
||||
}
|
||||
|
||||
type function struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Parameters map[string]interface{} `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
type toolChoice struct {
|
||||
Type string `json:"type"`
|
||||
Function function `json:"function"`
|
||||
}
|
||||
|
||||
type chatCompletionResponse struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Choices []chatCompletionChoice `json:"choices,omitempty"`
|
||||
Created int64 `json:"created,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
SystemFingerprint string `json:"system_fingerprint,omitempty"`
|
||||
Object string `json:"object,omitempty"`
|
||||
Usage chatCompletionUsage `json:"usage,omitempty"`
|
||||
}
|
||||
|
||||
type chatCompletionChoice struct {
|
||||
Index int `json:"index"`
|
||||
Message *chatMessage `json:"message,omitempty"`
|
||||
Delta *chatMessage `json:"delta,omitempty"`
|
||||
FinishReason string `json:"finish_reason,omitempty"`
|
||||
}
|
||||
|
||||
type chatCompletionUsage struct {
|
||||
PromptTokens int `json:"prompt_tokens,omitempty"`
|
||||
CompletionTokens int `json:"completion_tokens,omitempty"`
|
||||
TotalTokens int `json:"total_tokens,omitempty"`
|
||||
}
|
||||
|
||||
type chatMessage struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Role string `json:"role,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ToolCalls []toolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
func (m *chatMessage) IsEmpty() bool {
|
||||
if m.Content != "" {
|
||||
return false
|
||||
}
|
||||
if len(m.ToolCalls) != 0 {
|
||||
nonEmpty := false
|
||||
for _, toolCall := range m.ToolCalls {
|
||||
if !toolCall.Function.IsEmpty() {
|
||||
nonEmpty = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if nonEmpty {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type toolCall struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Function functionCall `json:"function"`
|
||||
}
|
||||
|
||||
type functionCall struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Arguments string `json:"arguments"`
|
||||
}
|
||||
|
||||
func (m *functionCall) IsEmpty() bool {
|
||||
return m.Name == "" && m.Arguments == ""
|
||||
}
|
||||
|
||||
type streamEvent struct {
|
||||
Id string `json:"id"`
|
||||
Event string `json:"event"`
|
||||
Data string `json:"data"`
|
||||
HttpStatus string `json:"http_status"`
|
||||
}
|
||||
|
||||
func (e *streamEvent) setValue(key, value string) {
|
||||
switch key {
|
||||
case streamEventIdItemKey:
|
||||
e.Id = value
|
||||
case streamEventNameItemKey:
|
||||
e.Event = value
|
||||
case streamDataItemKey:
|
||||
e.Data = value
|
||||
case streamBuiltInItemKey:
|
||||
if strings.HasPrefix(value, streamHttpStatusValuePrefix) {
|
||||
e.HttpStatus = value[len(streamHttpStatusValuePrefix):]
|
||||
}
|
||||
}
|
||||
}
|
||||
152
plugins/wasm-go/extensions/ai-proxy/provider/moonshot.go
Normal file
152
plugins/wasm-go/extensions/ai-proxy/provider/moonshot.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// moonshotProvider is the provider for Moonshot AI service.
|
||||
|
||||
const (
|
||||
moonshotDomain = "api.moonshot.cn"
|
||||
moonshotChatCompletionPath = "/v1/chat/completions"
|
||||
)
|
||||
|
||||
type moonshotProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *moonshotProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
if config.moonshotFileId != "" && config.context != nil {
|
||||
return errors.New("moonshotFileId and context cannot be configured at the same time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *moonshotProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &moonshotProvider{
|
||||
config: config,
|
||||
client: wrapper.NewClusterClient(wrapper.RouteCluster{
|
||||
Host: moonshotDomain,
|
||||
}),
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type moonshotProvider struct {
|
||||
config ProviderConfig
|
||||
|
||||
client wrapper.HttpClient
|
||||
fileContent string
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *moonshotProvider) GetProviderType() string {
|
||||
return providerTypeMoonshot
|
||||
}
|
||||
|
||||
func (m *moonshotProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(moonshotChatCompletionPath)
|
||||
_ = util.OverwriteRequestHost(moonshotDomain)
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken())
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *moonshotProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
|
||||
model := request.Model
|
||||
if model == "" {
|
||||
return types.ActionContinue, errors.New("missing model in chat completion request")
|
||||
}
|
||||
mappedModel := getMappedModel(model, m.config.modelMapping, log)
|
||||
if mappedModel == "" {
|
||||
return types.ActionContinue, errors.New("model becomes empty after applying the configured mapping")
|
||||
}
|
||||
request.Model = mappedModel
|
||||
|
||||
if m.config.moonshotFileId == "" && m.contextCache == nil {
|
||||
return types.ActionContinue, replaceJsonRequestBody(request, log)
|
||||
}
|
||||
|
||||
err := m.getContextContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
return
|
||||
}
|
||||
err = m.performChatCompletion(ctx, content, request, log)
|
||||
if err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to perform chat completion: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
|
||||
func (m *moonshotProvider) performChatCompletion(ctx wrapper.HttpContext, fileContent string, request *chatCompletionRequest, log wrapper.Log) error {
|
||||
insertContextMessage(request, fileContent)
|
||||
return replaceJsonRequestBody(request, log)
|
||||
}
|
||||
|
||||
func (m *moonshotProvider) getContextContent(callback func(string, error), log wrapper.Log) error {
|
||||
if m.config.moonshotFileId != "" {
|
||||
if m.fileContent != "" {
|
||||
callback(m.fileContent, nil)
|
||||
return nil
|
||||
}
|
||||
return m.sendRequest(http.MethodGet, "/v1/files/"+m.config.moonshotFileId+"/content", "",
|
||||
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||
responseString := string(responseBody)
|
||||
if statusCode != http.StatusOK {
|
||||
log.Errorf("failed to load knowledge base file from AI service, status: %d body: %s", statusCode, responseString)
|
||||
callback("", fmt.Errorf("failed to load knowledge base file from moonshot service, status: %d", statusCode))
|
||||
return
|
||||
}
|
||||
responseJson := gjson.Parse(responseString)
|
||||
m.fileContent = responseJson.Get("content").String()
|
||||
callback(m.fileContent, nil)
|
||||
})
|
||||
}
|
||||
|
||||
if m.contextCache != nil {
|
||||
return m.contextCache.GetContent(callback, log)
|
||||
}
|
||||
|
||||
return errors.New("both moonshotFileId and context are not configured")
|
||||
}
|
||||
|
||||
func (m *moonshotProvider) sendRequest(method, path string, body string, callback wrapper.ResponseCallback) error {
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
headers := util.CreateHeaders("Authorization", "Bearer "+m.config.GetRandomToken())
|
||||
return m.client.Get(path, headers, callback, m.config.timeout)
|
||||
case http.MethodPost:
|
||||
headers := util.CreateHeaders("Authorization", "Bearer "+m.config.GetRandomToken(), "Content-Type", "application/json")
|
||||
return m.client.Post(path, headers, []byte(body), callback, m.config.timeout)
|
||||
default:
|
||||
return errors.New("unsupported method: " + method)
|
||||
}
|
||||
}
|
||||
87
plugins/wasm-go/extensions/ai-proxy/provider/openai.go
Normal file
87
plugins/wasm-go/extensions/ai-proxy/provider/openai.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
// openaiProvider is the provider for OpenAI service.
|
||||
|
||||
const (
|
||||
openaiDomain = "api.openai.com"
|
||||
openaiChatCompletionPath = "/v1/chat/completions"
|
||||
)
|
||||
|
||||
type openaiProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *openaiProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *openaiProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &openaiProvider{
|
||||
config: config,
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type openaiProvider struct {
|
||||
config ProviderConfig
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *openaiProvider) GetProviderType() string {
|
||||
return providerTypeOpenAI
|
||||
}
|
||||
|
||||
func (m *openaiProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(openaiChatCompletionPath)
|
||||
_ = util.OverwriteRequestHost(openaiDomain)
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken())
|
||||
|
||||
if m.contextCache == nil {
|
||||
ctx.DontReadRequestBody()
|
||||
} else {
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
}
|
||||
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *openaiProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
if m.contextCache == nil {
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
insertContextMessage(request, content)
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
218
plugins/wasm-go/extensions/ai-proxy/provider/provider.go
Normal file
218
plugins/wasm-go/extensions/ai-proxy/provider/provider.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type ApiName string
|
||||
type Pointcut string
|
||||
|
||||
const (
|
||||
ApiNameChatCompletion ApiName = "chatCompletion"
|
||||
|
||||
providerTypeMoonshot = "moonshot"
|
||||
providerTypeAzure = "azure"
|
||||
providerTypeQwen = "qwen"
|
||||
providerTypeOpenAI = "openai"
|
||||
providerTypeGroq = "groq"
|
||||
providerTypeBaichuan = "baichuan"
|
||||
providerTypeYi = "yi"
|
||||
providerTypeDeepSeek = "deepseek"
|
||||
|
||||
protocolOpenAI = "openai"
|
||||
protocolOriginal = "original"
|
||||
|
||||
roleSystem = "system"
|
||||
|
||||
ctxKeyIncrementalStreaming = "incrementalStreaming"
|
||||
ctxKeyStreamingBody = "streamingBody"
|
||||
ctxKeyOriginalRequestModel = "originalRequestModel"
|
||||
ctxKeyFinalRequestModel = "finalRequestModel"
|
||||
ctxKeyPushedMessage = "pushedMessage"
|
||||
|
||||
objectChatCompletion = "chat.completion"
|
||||
objectChatCompletionChunk = "chat.completion.chunk"
|
||||
|
||||
wildcard = "*"
|
||||
|
||||
defaultTimeout = 2 * 60 * 1000 // ms
|
||||
)
|
||||
|
||||
type providerInitializer interface {
|
||||
ValidateConfig(ProviderConfig) error
|
||||
CreateProvider(ProviderConfig) (Provider, error)
|
||||
}
|
||||
|
||||
var (
|
||||
errUnsupportedApiName = errors.New("unsupported API name")
|
||||
|
||||
providerInitializers = map[string]providerInitializer{
|
||||
providerTypeMoonshot: &moonshotProviderInitializer{},
|
||||
providerTypeAzure: &azureProviderInitializer{},
|
||||
providerTypeQwen: &qwenProviderInitializer{},
|
||||
providerTypeOpenAI: &openaiProviderInitializer{},
|
||||
providerTypeGroq: &groqProviderInitializer{},
|
||||
providerTypeBaichuan: &baichuanProviderInitializer{},
|
||||
providerTypeYi: &yiProviderInitializer{},
|
||||
providerTypeDeepSeek: &deepseekProviderInitializer{},
|
||||
}
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
GetProviderType() string
|
||||
}
|
||||
|
||||
type RequestHeadersHandler interface {
|
||||
OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error)
|
||||
}
|
||||
|
||||
type RequestBodyHandler interface {
|
||||
OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error)
|
||||
}
|
||||
|
||||
type ResponseHeadersHandler interface {
|
||||
OnResponseHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error)
|
||||
}
|
||||
|
||||
type StreamingResponseBodyHandler interface {
|
||||
OnStreamingResponseBody(ctx wrapper.HttpContext, name ApiName, chunk []byte, isLastChunk bool, log wrapper.Log) ([]byte, error)
|
||||
}
|
||||
|
||||
type ResponseBodyHandler interface {
|
||||
OnResponseBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error)
|
||||
}
|
||||
|
||||
type ProviderConfig struct {
|
||||
// @Title zh-CN AI服务提供商
|
||||
// @Description zh-CN AI服务提供商类型,目前支持的取值为:"moonshot"、"qwen"、"openai"、"azure"、"baichuan"、"yi"
|
||||
typ string `required:"true" yaml:"type" json:"type"`
|
||||
// @Title zh-CN API Tokens
|
||||
// @Description zh-CN 在请求AI服务时用于认证的API Token列表。不同的AI服务提供商可能有不同的名称。部分供应商只支持配置一个API Token(如Azure OpenAI)。
|
||||
apiTokens []string `required:"false" yaml:"apiToken" json:"apiTokens"`
|
||||
// @Title zh-CN 请求超时
|
||||
// @Description zh-CN 请求AI服务的超时时间,单位为毫秒。默认值为120000,即2分钟
|
||||
timeout uint32 `required:"false" yaml:"timeout" json:"timeout"`
|
||||
// @Title zh-CN Moonshot File ID
|
||||
// @Description zh-CN 仅适用于Moonshot AI服务。Moonshot AI服务的文件ID,其内容用于补充AI请求上下文
|
||||
moonshotFileId string `required:"false" yaml:"moonshotFileId" json:"moonshotFileId"`
|
||||
// @Title zh-CN Azure OpenAI Service URL
|
||||
// @Description zh-CN 仅适用于Azure OpenAI服务。要请求的OpenAI服务的完整URL,包含api-version等参数
|
||||
azureServiceUrl string `required:"false" yaml:"azureServiceUrl" json:"azureServiceUrl"`
|
||||
// @Title zh-CN 通义千问File ID
|
||||
// @Description zh-CN 仅适用于通义千问服务。上传到Dashscope的文件ID,其内容用于补充AI请求上下文。仅支持qwen-long模型。
|
||||
qwenFileIds []string `required:"false" yaml:"qwenFileIds" json:"qwenFileIds"`
|
||||
// @Title zh-CN 启用通义千问搜索服务
|
||||
// @Description zh-CN 仅适用于通义千问服务,表示是否启用通义千问的互联网搜索功能。
|
||||
qwenEnableSearch bool `required:"false" yaml:"qwenEnableSearch" json:"qwenEnableSearch"`
|
||||
// @Title zh-CN 模型名称映射表
|
||||
// @Description zh-CN 用于将请求中的模型名称映射为目标AI服务商支持的模型名称。支持通过“*”来配置全局映射
|
||||
modelMapping map[string]string `required:"false" yaml:"modelMapping" json:"modelMapping"`
|
||||
// @Title zh-CN 对外接口协议
|
||||
// @Description zh-CN 通过本插件对外提供的AI服务接口协议。默认值为“openai”,即OpenAI的接口协议。如需保留原有接口协议,可配置为“original"
|
||||
protocol string `required:"false" yaml:"protocol" json:"protocol"`
|
||||
// @Title zh-CN 模型对话上下文
|
||||
// @Description zh-CN 配置一个外部获取对话上下文的文件来源,用于在AI请求中补充对话上下文
|
||||
context *ContextConfig `required:"false" yaml:"context" json:"context"`
|
||||
}
|
||||
|
||||
func (c *ProviderConfig) FromJson(json gjson.Result) {
|
||||
c.typ = json.Get("type").String()
|
||||
c.apiTokens = make([]string, 0)
|
||||
for _, token := range json.Get("apiTokens").Array() {
|
||||
c.apiTokens = append(c.apiTokens, token.String())
|
||||
}
|
||||
c.timeout = uint32(json.Get("timeout").Uint())
|
||||
if c.timeout == 0 {
|
||||
c.timeout = defaultTimeout
|
||||
}
|
||||
c.moonshotFileId = json.Get("moonshotFileId").String()
|
||||
c.azureServiceUrl = json.Get("azureServiceUrl").String()
|
||||
c.qwenFileIds = make([]string, 0)
|
||||
for _, fileId := range json.Get("qwenFileIds").Array() {
|
||||
c.qwenFileIds = append(c.qwenFileIds, fileId.String())
|
||||
}
|
||||
c.qwenEnableSearch = json.Get("qwenEnableSearch").Bool()
|
||||
c.modelMapping = make(map[string]string)
|
||||
for k, v := range json.Get("modelMapping").Map() {
|
||||
c.modelMapping[k] = v.String()
|
||||
}
|
||||
c.protocol = json.Get("protocol").String()
|
||||
if c.protocol == "" {
|
||||
c.protocol = protocolOpenAI
|
||||
}
|
||||
contextJson := json.Get("context")
|
||||
if contextJson.Exists() {
|
||||
c.context = &ContextConfig{}
|
||||
c.context.FromJson(contextJson)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ProviderConfig) Validate() error {
|
||||
if c.apiTokens == nil || len(c.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
if c.timeout < 0 {
|
||||
return errors.New("invalid timeout in config")
|
||||
}
|
||||
if c.protocol != protocolOpenAI && c.protocol != protocolOriginal {
|
||||
return errors.New("invalid protocol in config")
|
||||
}
|
||||
if c.context != nil {
|
||||
if err := c.context.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.typ == "" {
|
||||
return errors.New("missing type in provider config")
|
||||
}
|
||||
initializer, has := providerInitializers[c.typ]
|
||||
if !has {
|
||||
return errors.New("unknown provider type: " + c.typ)
|
||||
}
|
||||
if err := initializer.ValidateConfig(*c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ProviderConfig) GetRandomToken() string {
|
||||
apiTokens := c.apiTokens
|
||||
count := len(apiTokens)
|
||||
switch count {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
return apiTokens[0]
|
||||
default:
|
||||
return apiTokens[rand.Intn(count)]
|
||||
}
|
||||
}
|
||||
|
||||
func CreateProvider(pc ProviderConfig) (Provider, error) {
|
||||
initializer, has := providerInitializers[pc.typ]
|
||||
if !has {
|
||||
return nil, errors.New("unknown provider type: " + pc.typ)
|
||||
}
|
||||
return initializer.CreateProvider(pc)
|
||||
}
|
||||
|
||||
func getMappedModel(model string, modelMapping map[string]string, log wrapper.Log) string {
|
||||
if modelMapping == nil || len(modelMapping) == 0 {
|
||||
return model
|
||||
}
|
||||
if v, ok := modelMapping[model]; ok && len(v) != 0 {
|
||||
log.Debugf("model %s is mapped to %s explictly", model, v)
|
||||
return v
|
||||
}
|
||||
if v, ok := modelMapping[wildcard]; ok {
|
||||
log.Debugf("model %s is mapped to %s via wildcard", model, v)
|
||||
return v
|
||||
}
|
||||
return model
|
||||
}
|
||||
518
plugins/wasm-go/extensions/ai-proxy/provider/qwen.go
Normal file
518
plugins/wasm-go/extensions/ai-proxy/provider/qwen.go
Normal file
@@ -0,0 +1,518 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
// qwenProvider is the provider for Qwen service.
|
||||
|
||||
const (
|
||||
qwenResultFormatMessage = "message"
|
||||
|
||||
qwenDomain = "dashscope.aliyuncs.com"
|
||||
qwenChatCompletionPath = "/api/v1/services/aigc/text-generation/generation"
|
||||
|
||||
qwenTopPMin = 0.000001
|
||||
qwenTopPMax = 0.999999
|
||||
|
||||
qwenDummySystemMessageContent = "You are a helpful assistant."
|
||||
)
|
||||
|
||||
type qwenProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *qwenProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
if len(config.qwenFileIds) != 0 && config.context != nil {
|
||||
return errors.New("qwenFileIds and context cannot be configured at the same time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *qwenProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &qwenProvider{
|
||||
config: config,
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type qwenProvider struct {
|
||||
config ProviderConfig
|
||||
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *qwenProvider) GetProviderType() string {
|
||||
return providerTypeQwen
|
||||
}
|
||||
|
||||
func (m *qwenProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(qwenChatCompletionPath)
|
||||
_ = util.OverwriteRequestHost(qwenDomain)
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken())
|
||||
|
||||
if m.config.protocol == protocolOriginal && m.config.context == nil {
|
||||
ctx.DontReadRequestBody()
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Accept-Encoding")
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
|
||||
// Delay the header processing to allow changing streaming mode in OnRequestBody
|
||||
return types.HeaderStopIteration, nil
|
||||
}
|
||||
|
||||
func (m *qwenProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
|
||||
if m.config.protocol == protocolOriginal {
|
||||
if m.config.context == nil {
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
request := &qwenTextGenRequest{}
|
||||
if err := json.Unmarshal(body, request); err != nil {
|
||||
return types.ActionContinue, fmt.Errorf("unable to unmarshal request: %v", err)
|
||||
}
|
||||
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
m.insertContextMessage(request, content)
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
|
||||
model := request.Model
|
||||
if model == "" {
|
||||
return types.ActionContinue, errors.New("missing model in chat completion request")
|
||||
}
|
||||
ctx.SetContext(ctxKeyOriginalRequestModel, model)
|
||||
mappedModel := getMappedModel(model, m.config.modelMapping, log)
|
||||
if mappedModel == "" {
|
||||
return types.ActionContinue, errors.New("model becomes empty after applying the configured mapping")
|
||||
}
|
||||
request.Model = mappedModel
|
||||
ctx.SetContext(ctxKeyFinalRequestModel, request.Model)
|
||||
|
||||
streaming := request.Stream
|
||||
if streaming {
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Accept", "text/event-stream")
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("X-DashScope-SSE", "enable")
|
||||
} else {
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Accept", "*/*")
|
||||
_ = proxywasm.RemoveHttpRequestHeader("X-DashScope-SSE")
|
||||
}
|
||||
|
||||
if m.config.context == nil {
|
||||
qwenRequest := m.buildQwenTextGenerationRequest(request, streaming)
|
||||
if streaming {
|
||||
ctx.SetContext(ctxKeyIncrementalStreaming, qwenRequest.Parameters.IncrementalOutput)
|
||||
}
|
||||
return types.ActionContinue, replaceJsonRequestBody(qwenRequest, log)
|
||||
}
|
||||
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
insertContextMessage(request, content)
|
||||
qwenRequest := m.buildQwenTextGenerationRequest(request, streaming)
|
||||
if streaming {
|
||||
ctx.SetContext(ctxKeyIncrementalStreaming, qwenRequest.Parameters.IncrementalOutput)
|
||||
}
|
||||
if err := replaceJsonRequestBody(qwenRequest, log); err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
|
||||
func (m *qwenProvider) OnResponseHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if m.config.protocol == protocolOriginal {
|
||||
ctx.DontReadResponseBody()
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
_ = proxywasm.RemoveHttpResponseHeader("Content-Length")
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *qwenProvider) OnStreamingResponseBody(ctx wrapper.HttpContext, name ApiName, chunk []byte, isLastChunk bool, log wrapper.Log) ([]byte, error) {
|
||||
receivedBody := chunk
|
||||
if bufferedStreamingBody, has := ctx.GetContext(ctxKeyStreamingBody).([]byte); has {
|
||||
receivedBody = append(bufferedStreamingBody, chunk...)
|
||||
}
|
||||
|
||||
incrementalStreaming, err := ctx.GetContext(ctxKeyIncrementalStreaming).(bool)
|
||||
if !err {
|
||||
incrementalStreaming = false
|
||||
}
|
||||
|
||||
eventStartIndex, lineStartIndex, valueStartIndex := -1, -1, -1
|
||||
|
||||
defer func() {
|
||||
if eventStartIndex >= 0 && eventStartIndex < len(receivedBody) {
|
||||
// Just in case the received chunk is not a complete event.
|
||||
ctx.SetContext(ctxKeyStreamingBody, receivedBody[eventStartIndex:])
|
||||
} else {
|
||||
ctx.SetContext(ctxKeyStreamingBody, nil)
|
||||
}
|
||||
}()
|
||||
|
||||
// Sample Qwen event response:
|
||||
//
|
||||
// event:result
|
||||
// :HTTP_STATUS/200
|
||||
// data:{"output":{"choices":[{"message":{"content":"你好!","role":"assistant"},"finish_reason":"null"}]},"usage":{"total_tokens":116,"input_tokens":114,"output_tokens":2},"request_id":"71689cfc-1f42-9949-86e8-9563b7f832b1"}
|
||||
//
|
||||
// event:error
|
||||
// :HTTP_STATUS/400
|
||||
// data:{"code":"InvalidParameter","message":"Preprocessor error","request_id":"0cbe6006-faec-9854-bf8b-c906d75c3bd8"}
|
||||
//
|
||||
|
||||
var responseBuilder strings.Builder
|
||||
currentKey := ""
|
||||
currentEvent := &streamEvent{}
|
||||
i, length := 0, len(receivedBody)
|
||||
for i = 0; i < length; i++ {
|
||||
ch := receivedBody[i]
|
||||
if ch != '\n' {
|
||||
if lineStartIndex == -1 {
|
||||
if eventStartIndex == -1 {
|
||||
eventStartIndex = i
|
||||
}
|
||||
lineStartIndex = i
|
||||
valueStartIndex = -1
|
||||
}
|
||||
if valueStartIndex == -1 {
|
||||
if ch == ':' {
|
||||
valueStartIndex = i + 1
|
||||
currentKey = string(receivedBody[lineStartIndex:valueStartIndex])
|
||||
}
|
||||
} else if valueStartIndex == i && ch == ' ' {
|
||||
// Skip leading spaces in data.
|
||||
valueStartIndex = i + 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if lineStartIndex != -1 {
|
||||
value := string(receivedBody[valueStartIndex:i])
|
||||
currentEvent.setValue(currentKey, value)
|
||||
} else {
|
||||
// Extra new line. The current event is complete.
|
||||
log.Debugf("processing event: %v", currentEvent)
|
||||
if err := m.convertStreamEvent(ctx, &responseBuilder, currentEvent, incrementalStreaming, log); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Reset event parsing state.
|
||||
eventStartIndex = -1
|
||||
currentEvent = &streamEvent{}
|
||||
}
|
||||
|
||||
// Reset line parsing state.
|
||||
lineStartIndex = -1
|
||||
valueStartIndex = -1
|
||||
currentKey = ""
|
||||
}
|
||||
|
||||
modifiedResponseChunk := responseBuilder.String()
|
||||
log.Debugf("=== modified response chunk: %s", modifiedResponseChunk)
|
||||
return []byte(modifiedResponseChunk), nil
|
||||
}
|
||||
|
||||
func (m *qwenProvider) OnResponseBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
qwenResponse := &qwenTextGenResponse{}
|
||||
if err := json.Unmarshal(body, qwenResponse); err != nil {
|
||||
return types.ActionContinue, fmt.Errorf("unable to unmarshal Qwen response: %v", err)
|
||||
}
|
||||
response := m.buildChatCompletionResponse(ctx, qwenResponse)
|
||||
return types.ActionContinue, replaceJsonResponseBody(response, log)
|
||||
}
|
||||
|
||||
func (m *qwenProvider) buildQwenTextGenerationRequest(origRequest *chatCompletionRequest, streaming bool) *qwenTextGenRequest {
|
||||
messages := make([]qwenMessage, 0, len(origRequest.Messages))
|
||||
for i := range origRequest.Messages {
|
||||
messages = append(messages, chatMessage2QwenMessage(origRequest.Messages[i]))
|
||||
}
|
||||
request := &qwenTextGenRequest{
|
||||
Model: origRequest.Model,
|
||||
Input: qwenTextGenInput{
|
||||
Messages: messages,
|
||||
},
|
||||
Parameters: qwenTextGenParameters{
|
||||
ResultFormat: qwenResultFormatMessage,
|
||||
MaxTokens: origRequest.MaxTokens,
|
||||
N: origRequest.N,
|
||||
Seed: origRequest.Seed,
|
||||
Temperature: origRequest.Temperature,
|
||||
TopP: math.Max(qwenTopPMin, math.Min(origRequest.TopP, qwenTopPMax)),
|
||||
IncrementalOutput: streaming && (origRequest.Tools == nil || len(origRequest.Tools) == 0),
|
||||
EnableSearch: m.config.qwenEnableSearch,
|
||||
Tools: origRequest.Tools,
|
||||
},
|
||||
}
|
||||
if len(m.config.qwenFileIds) != 0 {
|
||||
builder := strings.Builder{}
|
||||
for _, fileId := range m.config.qwenFileIds {
|
||||
if builder.Len() != 0 {
|
||||
builder.WriteRune(',')
|
||||
}
|
||||
builder.WriteString("fileid://")
|
||||
builder.WriteString(fileId)
|
||||
}
|
||||
contextMessageId := m.insertContextMessage(request, builder.String())
|
||||
if contextMessageId == 0 {
|
||||
// The context message cannot come first. We need to add another dummy system message before it.
|
||||
request.Input.Messages = append([]qwenMessage{{Role: roleSystem, Content: qwenDummySystemMessageContent}}, request.Input.Messages...)
|
||||
}
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
func (m *qwenProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, qwenResponse *qwenTextGenResponse) *chatCompletionResponse {
|
||||
choices := make([]chatCompletionChoice, 0, len(qwenResponse.Output.Choices))
|
||||
for _, qwenChoice := range qwenResponse.Output.Choices {
|
||||
message := qwenMessageToChatMessage(qwenChoice.Message)
|
||||
choices = append(choices, chatCompletionChoice{
|
||||
Message: &message,
|
||||
FinishReason: qwenChoice.FinishReason,
|
||||
})
|
||||
}
|
||||
return &chatCompletionResponse{
|
||||
Id: qwenResponse.RequestId,
|
||||
Created: time.Now().UnixMilli() / 1000,
|
||||
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string),
|
||||
SystemFingerprint: "",
|
||||
Object: objectChatCompletion,
|
||||
Choices: choices,
|
||||
Usage: chatCompletionUsage{
|
||||
PromptTokens: qwenResponse.Usage.InputTokens,
|
||||
CompletionTokens: qwenResponse.Usage.OutputTokens,
|
||||
TotalTokens: qwenResponse.Usage.TotalTokens,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *qwenProvider) buildChatCompletionStreamingResponse(ctx wrapper.HttpContext, qwenResponse *qwenTextGenResponse, incrementalStreaming bool, log wrapper.Log) []*chatCompletionResponse {
|
||||
baseMessage := chatCompletionResponse{
|
||||
Id: qwenResponse.RequestId,
|
||||
Created: time.Now().UnixMilli() / 1000,
|
||||
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string),
|
||||
SystemFingerprint: "",
|
||||
Object: objectChatCompletionChunk,
|
||||
}
|
||||
|
||||
responses := make([]*chatCompletionResponse, 0)
|
||||
|
||||
qwenChoice := qwenResponse.Output.Choices[0]
|
||||
message := qwenChoice.Message
|
||||
|
||||
deltaMessage := &chatMessage{Role: message.Role, Content: message.Content, ToolCalls: append([]toolCall{}, message.ToolCalls...)}
|
||||
if !incrementalStreaming {
|
||||
if pushedMessage, ok := ctx.GetContext(ctxKeyPushedMessage).(qwenMessage); ok {
|
||||
deltaMessage.Content = util.StripPrefix(deltaMessage.Content, pushedMessage.Content)
|
||||
if len(deltaMessage.ToolCalls) > 0 && pushedMessage.ToolCalls != nil {
|
||||
for i, tc := range deltaMessage.ToolCalls {
|
||||
if i >= len(pushedMessage.ToolCalls) {
|
||||
break
|
||||
}
|
||||
pushedFunction := pushedMessage.ToolCalls[i].Function
|
||||
tc.Function.Id = util.StripPrefix(tc.Function.Id, pushedFunction.Id)
|
||||
tc.Function.Name = util.StripPrefix(tc.Function.Name, pushedFunction.Name)
|
||||
tc.Function.Arguments = util.StripPrefix(tc.Function.Arguments, pushedFunction.Arguments)
|
||||
deltaMessage.ToolCalls[i] = tc
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.SetContext(ctxKeyPushedMessage, message)
|
||||
}
|
||||
|
||||
if !deltaMessage.IsEmpty() {
|
||||
deltaResponse := *&baseMessage
|
||||
deltaResponse.Choices = append(deltaResponse.Choices, chatCompletionChoice{Delta: deltaMessage})
|
||||
responses = append(responses, &deltaResponse)
|
||||
}
|
||||
|
||||
// Yes, Qwen uses a string "null" as null.
|
||||
if qwenChoice.FinishReason != "" && qwenChoice.FinishReason != "null" {
|
||||
finishResponse := *&baseMessage
|
||||
finishResponse.Choices = append(finishResponse.Choices, chatCompletionChoice{FinishReason: qwenChoice.FinishReason})
|
||||
responses = append(responses, &finishResponse)
|
||||
}
|
||||
|
||||
return responses
|
||||
}
|
||||
|
||||
func (m *qwenProvider) convertStreamEvent(ctx wrapper.HttpContext, responseBuilder *strings.Builder, event *streamEvent, incrementalStreaming bool, log wrapper.Log) error {
|
||||
if event.Data == streamEndDataValue {
|
||||
m.appendStreamEvent(responseBuilder, event)
|
||||
return nil
|
||||
}
|
||||
|
||||
if event.Event != eventResult || event.HttpStatus != httpStatus200 {
|
||||
// Something goes wrong. Just pass through the event.
|
||||
m.appendStreamEvent(responseBuilder, event)
|
||||
return nil
|
||||
}
|
||||
|
||||
qwenResponse := &qwenTextGenResponse{}
|
||||
if err := json.Unmarshal([]byte(event.Data), qwenResponse); err != nil {
|
||||
log.Errorf("unable to unmarshal Qwen response: %v", err)
|
||||
return fmt.Errorf("unable to unmarshal Qwen response: %v", err)
|
||||
}
|
||||
|
||||
responses := m.buildChatCompletionStreamingResponse(ctx, qwenResponse, incrementalStreaming, log)
|
||||
for _, response := range responses {
|
||||
responseBody, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
log.Errorf("unable to marshal response: %v", err)
|
||||
return fmt.Errorf("unable to marshal response: %v", err)
|
||||
}
|
||||
modifiedEvent := &*event
|
||||
modifiedEvent.Data = string(responseBody)
|
||||
m.appendStreamEvent(responseBuilder, modifiedEvent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *qwenProvider) insertContextMessage(request *qwenTextGenRequest, content string) int {
|
||||
fileMessage := qwenMessage{
|
||||
Role: roleSystem,
|
||||
Content: content,
|
||||
}
|
||||
firstNonSystemMessageIndex := -1
|
||||
messages := request.Input.Messages
|
||||
if messages != nil {
|
||||
for i, message := range request.Input.Messages {
|
||||
if message.Role != roleSystem {
|
||||
firstNonSystemMessageIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if firstNonSystemMessageIndex == -1 {
|
||||
request.Input.Messages = append([]qwenMessage{fileMessage}, request.Input.Messages...)
|
||||
return 0
|
||||
} else {
|
||||
request.Input.Messages = append(request.Input.Messages[:firstNonSystemMessageIndex], append([]qwenMessage{fileMessage}, request.Input.Messages[firstNonSystemMessageIndex:]...)...)
|
||||
return firstNonSystemMessageIndex
|
||||
}
|
||||
}
|
||||
|
||||
func (m *qwenProvider) appendStreamEvent(responseBuilder *strings.Builder, event *streamEvent) {
|
||||
responseBuilder.WriteString(streamDataItemKey)
|
||||
responseBuilder.WriteString(event.Data)
|
||||
responseBuilder.WriteString("\n\n")
|
||||
}
|
||||
|
||||
type qwenTextGenRequest struct {
|
||||
Model string `json:"model"`
|
||||
Input qwenTextGenInput `json:"input"`
|
||||
Parameters qwenTextGenParameters `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
type qwenTextGenInput struct {
|
||||
Messages []qwenMessage `json:"messages"`
|
||||
}
|
||||
|
||||
type qwenTextGenParameters struct {
|
||||
ResultFormat string `json:"result_format,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
RepetitionPenalty float64 `json:"repetition_penalty,omitempty"`
|
||||
N int `json:"n,omitempty"`
|
||||
Seed int `json:"seed,omitempty"`
|
||||
Temperature float64 `json:"temperature,omitempty"`
|
||||
TopP float64 `json:"top_p,omitempty"`
|
||||
IncrementalOutput bool `json:"incremental_output,omitempty"`
|
||||
EnableSearch bool `json:"enable_search,omitempty"`
|
||||
Tools []tool `json:"tools,omitempty"`
|
||||
}
|
||||
|
||||
type qwenTextGenResponse struct {
|
||||
RequestId string `json:"request_id"`
|
||||
Output qwenTextGenOutput `json:"output"`
|
||||
Usage qwenTextGenUsage `json:"usage"`
|
||||
}
|
||||
|
||||
type qwenTextGenOutput struct {
|
||||
FinishReason string `json:"finish_reason"`
|
||||
Choices []qwenTextGenChoice `json:"choices"`
|
||||
}
|
||||
|
||||
type qwenTextGenChoice struct {
|
||||
FinishReason string `json:"finish_reason"`
|
||||
Message qwenMessage `json:"message"`
|
||||
}
|
||||
|
||||
type qwenTextGenUsage struct {
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
}
|
||||
|
||||
type qwenMessage struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
ToolCalls []toolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
func qwenMessageToChatMessage(qwenMessage qwenMessage) chatMessage {
|
||||
return chatMessage{
|
||||
Name: qwenMessage.Name,
|
||||
Role: qwenMessage.Role,
|
||||
Content: qwenMessage.Content,
|
||||
ToolCalls: qwenMessage.ToolCalls,
|
||||
}
|
||||
}
|
||||
|
||||
func chatMessage2QwenMessage(chatMessage chatMessage) qwenMessage {
|
||||
return qwenMessage{
|
||||
Name: chatMessage.Name,
|
||||
Role: chatMessage.Role,
|
||||
Content: chatMessage.Content,
|
||||
ToolCalls: chatMessage.ToolCalls,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
)
|
||||
|
||||
func decodeChatCompletionRequest(body []byte, request *chatCompletionRequest) error {
|
||||
if err := json.Unmarshal(body, request); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal request: %v", err)
|
||||
}
|
||||
if request.Messages == nil || len(request.Messages) == 0 {
|
||||
return errors.New("no message found in the request body")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func replaceJsonRequestBody(request interface{}, log wrapper.Log) error {
|
||||
body, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal request: %v", err)
|
||||
}
|
||||
log.Debugf("request body: %s", string(body))
|
||||
err = proxywasm.ReplaceHttpRequestBody(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to replace the original request body: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func insertContextMessage(request *chatCompletionRequest, content string) {
|
||||
fileMessage := chatMessage{
|
||||
Role: roleSystem,
|
||||
Content: content,
|
||||
}
|
||||
firstNonSystemMessageIndex := -1
|
||||
for i, message := range request.Messages {
|
||||
if message.Role != roleSystem {
|
||||
firstNonSystemMessageIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if firstNonSystemMessageIndex == -1 {
|
||||
request.Messages = append([]chatMessage{fileMessage}, request.Messages...)
|
||||
} else {
|
||||
request.Messages = append(request.Messages[:firstNonSystemMessageIndex], append([]chatMessage{fileMessage}, request.Messages[firstNonSystemMessageIndex:]...)...)
|
||||
}
|
||||
}
|
||||
|
||||
func replaceJsonResponseBody(response interface{}, log wrapper.Log) error {
|
||||
body, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal response: %v", err)
|
||||
}
|
||||
log.Debugf("response body: %s", string(body))
|
||||
err = proxywasm.ReplaceHttpResponseBody(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to replace the original response body: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
85
plugins/wasm-go/extensions/ai-proxy/provider/yi.go
Normal file
85
plugins/wasm-go/extensions/ai-proxy/provider/yi.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
)
|
||||
|
||||
const (
|
||||
yiDomain = "api.lingyiwanwu.com"
|
||||
yiChatCompletionPath = "/v1/chat/completions"
|
||||
)
|
||||
|
||||
type yiProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *yiProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *yiProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
|
||||
return &yiProvider{
|
||||
config: config,
|
||||
contextCache: createContextCache(&config),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type yiProvider struct {
|
||||
config ProviderConfig
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *yiProvider) GetProviderType() string {
|
||||
return providerTypeYi
|
||||
}
|
||||
|
||||
func (m *yiProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
_ = util.OverwriteRequestPath(yiChatCompletionPath)
|
||||
_ = util.OverwriteRequestHost(yiDomain)
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken())
|
||||
|
||||
if m.contextCache == nil {
|
||||
ctx.DontReadRequestBody()
|
||||
} else {
|
||||
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
|
||||
}
|
||||
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
|
||||
func (m *yiProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
|
||||
if apiName != ApiNameChatCompletion {
|
||||
return types.ActionContinue, errUnsupportedApiName
|
||||
}
|
||||
if m.contextCache == nil {
|
||||
return types.ActionContinue, nil
|
||||
}
|
||||
request := &chatCompletionRequest{}
|
||||
if err := decodeChatCompletionRequest(body, request); err != nil {
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
err := m.contextCache.GetContent(func(content string, err error) {
|
||||
defer func() {
|
||||
_ = proxywasm.ResumeHttpRequest()
|
||||
}()
|
||||
if err != nil {
|
||||
log.Errorf("failed to load context file: %v", err)
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
|
||||
}
|
||||
insertContextMessage(request, content)
|
||||
if err := replaceJsonRequestBody(request, log); err != nil {
|
||||
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
|
||||
}
|
||||
}, log)
|
||||
if err == nil {
|
||||
return types.ActionPause, nil
|
||||
}
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
36
plugins/wasm-go/extensions/ai-proxy/util/http.go
Normal file
36
plugins/wasm-go/extensions/ai-proxy/util/http.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package util
|
||||
|
||||
import "github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
|
||||
const (
|
||||
HeaderContentType = "Content-Type"
|
||||
|
||||
MimeTypeTextPlain = "text/plain"
|
||||
MimeTypeApplicationJson = "application/json"
|
||||
)
|
||||
|
||||
func SendResponse(statusCode uint32, contentType, body string) error {
|
||||
return proxywasm.SendHttpResponse(statusCode, CreateHeaders(HeaderContentType, contentType), []byte(body), -1)
|
||||
}
|
||||
|
||||
func CreateHeaders(kvs ...string) [][2]string {
|
||||
headers := make([][2]string, 0, len(kvs)/2)
|
||||
for i := 0; i < len(kvs); i += 2 {
|
||||
headers = append(headers, [2]string{kvs[i], kvs[i+1]})
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func OverwriteRequestHost(host string) error {
|
||||
if originHost, err := proxywasm.GetHttpRequestHeader(":authority"); err == nil {
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("X-ENVOY-ORIGINAL-HOST", originHost)
|
||||
}
|
||||
return proxywasm.ReplaceHttpRequestHeader(":authority", host)
|
||||
}
|
||||
|
||||
func OverwriteRequestPath(path string) error {
|
||||
if originPath, err := proxywasm.GetHttpRequestHeader(":path"); err == nil {
|
||||
_ = proxywasm.ReplaceHttpRequestHeader("X-ENVOY-ORIGINAL-PATH", originPath)
|
||||
}
|
||||
return proxywasm.ReplaceHttpRequestHeader(":path", path)
|
||||
}
|
||||
22
plugins/wasm-go/extensions/ai-proxy/util/json.go
Normal file
22
plugins/wasm-go/extensions/ai-proxy/util/json.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func EscapeStringForJson(s string) string {
|
||||
var builder strings.Builder
|
||||
for _, c := range s { //iterate through rune
|
||||
switch c {
|
||||
case '"':
|
||||
builder.WriteRune('\\')
|
||||
builder.WriteRune(c)
|
||||
break
|
||||
default:
|
||||
quoted := strconv.QuoteRune(c)
|
||||
builder.WriteString(quoted[1 : len(quoted)-1])
|
||||
}
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
28
plugins/wasm-go/extensions/ai-proxy/util/json_test.go
Normal file
28
plugins/wasm-go/extensions/ai-proxy/util/json_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEscapeForJsonString(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input, output string
|
||||
}{
|
||||
{"hello", "hello"},
|
||||
{"hello\"world", "hello\\\"world"},
|
||||
{"h\be\vl\tlo\rworld\n", "h\\be\\vl\\tlo\\rworld\\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
// t.Run enables running "subtests", one for each
|
||||
// table entry. These are shown separately
|
||||
// when executing `go test -v`.
|
||||
testName := tt.input
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
output := EscapeStringForJson(tt.input)
|
||||
assert.Equal(t, tt.output, output)
|
||||
})
|
||||
}
|
||||
}
|
||||
8
plugins/wasm-go/extensions/ai-proxy/util/string.go
Normal file
8
plugins/wasm-go/extensions/ai-proxy/util/string.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package util
|
||||
|
||||
func StripPrefix(s string, prefix string) string {
|
||||
if len(prefix) != 0 && len(s) >= len(prefix) && s[0:len(prefix)] == prefix {
|
||||
return s[len(prefix):]
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
)
|
||||
|
||||
@@ -5,6 +5,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v1.3.2
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
github.com/wasilibs/go-re2 v1.4.1
|
||||
|
||||
@@ -6,6 +6,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
)
|
||||
|
||||
|
||||
9
plugins/wasm-go/extensions/cache-control/go.sum
Normal file
9
plugins/wasm-go/extensions/cache-control/go.sum
Normal file
@@ -0,0 +1,9 @@
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230629030002-81e467b6242d
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230519024024-625c06e58f91
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
)
|
||||
|
||||
@@ -6,6 +6,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230410091208-df60dd43079c
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
)
|
||||
|
||||
@@ -6,6 +6,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -8,7 +8,7 @@ replace github.com/wasilibs/nottinygc v0.5.1 => github.com/higress-group/nottiny
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.18
|
||||
replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -5,6 +5,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837
|
||||
)
|
||||
|
||||
@@ -7,6 +7,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20231017105619-a18879bf867c
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -7,7 +7,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230807053545-d307d0e755f1
|
||||
github.com/go-jose/go-jose/v3 v3.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/tidwall/gjson v1.14.3
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
)
|
||||
|
||||
@@ -15,6 +15,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
)
|
||||
|
||||
@@ -6,6 +6,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
|
||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user