Compare commits

...

46 Commits

Author SHA1 Message Date
澄潭
1c415c60c3 rel: Release v1.4.2 (#1159) 2024-07-26 13:55:03 +08:00
澄潭
59acb61926 Update Makefile.core.mk 2024-07-26 13:50:16 +08:00
澄潭
29079f4e2a Support set buffer limit (#1153) 2024-07-25 20:42:39 +08:00
澄潭
95edce024d support custom trace span tag (#1156) 2024-07-25 20:42:09 +08:00
Kent Dong
b6d07a157c feat: Always buffer request body in ai-proxy plugin (#1155) 2024-07-25 19:35:39 +08:00
澄潭
10569f49ae support keep original auth header (#1151) 2024-07-24 19:31:38 +08:00
Jun
2a588c99c7 fix: add full push when higress-https configmap updated and fix certmagic storage (#1105) 2024-07-24 19:30:40 +08:00
Kent Dong
0cfef34bff feat: Support fallback route in ai-proxy plugin (#1123) 2024-07-24 19:25:32 +08:00
rinfx
5c2b5d5750 potential bug fix (#1141) 2024-07-24 19:24:47 +08:00
rinfx
8f483518a9 support take effect on api level (#1150) 2024-07-24 19:23:55 +08:00
Kent Dong
f6ee4ed166 fix: Bypass the response body processing in ai-proxy if it is returned internally (#1149) 2024-07-24 16:18:00 +08:00
Kent Dong
9a9e924037 feat: Make higress-core and higress-gateway as the default container (#1144) 2024-07-24 11:25:16 +08:00
jiaomh
e7d66f691f chore: Update multiple dependencies to the latest version (#1143) 2024-07-23 11:48:44 +08:00
rinfx
8c48fcb423 update template decorator (#1142) 2024-07-22 17:04:55 +08:00
007gzs
ef31e09310 feat: add rust demo plugin request block (#1091)
Co-authored-by: Yi <lynskylate@gmail.com>
2024-07-22 15:49:06 +08:00
韩贤涛
c0f2cafdc8 feat: support ext_auth wasmplugin (#1103) 2024-07-17 15:30:32 +08:00
Kent Dong
d5a9ff3a98 fix: Fix possible type-casting related panics in ai-proxy plugin (#1127) 2024-07-16 18:38:43 +08:00
Kent Dong
f069ad5b0d feat: Add statusCodeDetails info when returning response in Wasm plugins directly (#1116) 2024-07-16 09:52:46 +08:00
xu.zhao
85219b6c53 fix: controller has no right to watch deployment (#1089)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-07-15 16:34:24 +08:00
mamba
5041277be3 feat: 🎸 add frontend gray plugin (#1120)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-07-15 15:47:04 +08:00
rinfx
c00c8827f9 support service-level match config (#1112) 2024-07-15 14:00:02 +08:00
rinfx
46218058d1 token-ratelimit crash bugfix (#1119) 2024-07-12 15:05:15 +08:00
Kent Dong
5306385e6b feat: Support loading custom parameters in build-and-push-wasm-plugin-image.yaml (#1118) 2024-07-12 14:23:12 +08:00
Se7en
4e881fdd3f doc: update cluster-key-rate-limit doc (#1113)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-07-11 17:31:14 +08:00
Kent Dong
59aa3b5488 fix: Use "controller.name" to refer the controller service in higress-config (#1108) 2024-07-11 16:14:16 +08:00
Kent Dong
c40cf85aad fix: Fix the incorrect image name used in build-and-push-wasm-plugin-image.yaml (#1109) 2024-07-10 13:51:13 +08:00
Kent Dong
7c749b864c fix: Fix some bugs in build-and-push-wasm-plugin-image.yaml (#1107) 2024-07-10 13:41:58 +08:00
Yiiong
74ddbf02f6 feat:add build-and-push-wasm-plugin-image.yaml (#1069)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-07-08 21:44:58 +08:00
zzjin
60c56a16ab Support CredentialConfig.TLSSecret with namespace. Resolve: #1066 (#1095)
Signed-off-by: zzjin <tczzjin@gmail.com>
2024-07-08 19:49:51 +08:00
Kent Dong
5a2c6835f7 feat: Support embeddings API for Qwen in the ai-proxy plugin (#1079) 2024-07-08 19:37:08 +08:00
Kent Dong
12a5612450 feat: Support model prefix mapping in ai-proxy (#1097) 2024-07-08 19:33:08 +08:00
nohup
b9f5c4d1f2 feat: support Cloudflare Workers AI (#1068)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-07-08 19:27:11 +08:00
Jun
d7bdcbd026 fix priorityClassName missed (#1096) 2024-07-08 19:26:08 +08:00
野生程序员
dd284d1f24 feat: loadBalancerClass (#1071) 2024-07-08 10:58:33 +08:00
jiaomh
a7ee523c98 Update test/README.md (#1098) 2024-07-07 10:06:32 +08:00
Kent Dong
4bbfb131ee feat: Load 3rd-party images from higress image repo (#1067) 2024-07-04 20:14:00 +08:00
Se7en
6fd71f9749 fix: prometheus port (#1076) 2024-07-03 13:46:32 +08:00
pepesi
e0159f501a fix jwt-auth plugin claims_to_headers failed (#1075) 2024-07-03 10:11:17 +08:00
Kent Dong
56226d5052 feat: Create an IngressClass resource in the helm chart (#1072) 2024-07-02 21:22:00 +08:00
pepesi
086a9cc973 fixed ai-statistics plugin statistics error (#1060) 2024-07-02 20:35:12 +08:00
Tao Jikun
e389313aa3 feat: update doc for running Ingress API conformance tests (#1065)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-06-27 14:55:10 +08:00
澄潭
f64c601264 compatiable with openai sdk (#1061)
Co-authored-by: Kent Dong <ch3cho@qq.com>
2024-06-27 09:30:52 +08:00
co63oc
9c6ea109f8 Fix typos (#1053) 2024-06-26 19:47:39 +08:00
yy
4ca2d23404 feat: helm charts support installing gateway in daemonset mod. (#1054) 2024-06-26 19:47:20 +08:00
co63oc
0ce52de59b Fix typos (#1050) 2024-06-22 16:22:03 +08:00
澄潭
81e459da01 Update Makefile.core.mk 2024-06-19 17:40:22 +08:00
210 changed files with 5250 additions and 977 deletions

View File

@@ -0,0 +1,114 @@
name: Build and Push Wasm Plugin Image
on:
push:
tags:
- "wasm-go-*-v*.*.*" # 匹配 wasm-go-{pluginName}-vX.Y.Z 格式的标签
workflow_dispatch:
inputs:
plugin_name:
description: 'Name of the plugin'
required: true
type: string
version:
description: 'Version of the plugin (optional, without leading v)'
required: false
type: string
jobs:
build-and-push-wasm-plugin-image:
runs-on: ubuntu-latest
environment:
name: image-registry-msg
env:
IMAGE_REGISTRY_SERVICE: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
IMAGE_REPOSITORY: ${{ vars.PLUGIN_IMAGE_REPOSITORY || 'plugins' }}
GO_VERSION: 1.19
TINYGO_VERSION: 0.28.1
ORAS_VERSION: 1.0.0
steps:
- name: Set plugin_name and version from inputs or ref_name
id: set_vars
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
plugin_name="${{ github.event.inputs.plugin_name }}"
version="${{ github.event.inputs.version }}"
else
ref_name=${{ github.ref_name }}
plugin_name=${ref_name#*-*-} # 删除插件名前面的字段(wasm-go-)
plugin_name=${plugin_name%-*} # 删除插件名后面的字段(-vX.Y.Z)
version=$(echo "$ref_name" | awk -F'v' '{print $2}')
fi
echo "PLUGIN_NAME=$plugin_name" >> $GITHUB_ENV
echo "VERSION=$version" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v3
- name: File Check
run: |
workspace=${{ github.workspace }}/plugins/wasm-go/extensions/${PLUGIN_NAME}
push_command="./plugin.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip"
# 查找spec.yaml
if [ -f "${workspace}/spec.yaml" ]; then
echo "spec.yaml exists"
push_command="./spec.yaml:application/vnd.module.wasm.spec.v1+yaml $push_command "
fi
# 查找README.md
if [ -f "${workspace}/README.md" ];then
echo "README.md exists"
push_command="./README.md:application/vnd.module.wasm.doc.v1+markdown $push_command "
fi
# 查找README_{lang}.md
for file in ${workspace}/README_*.md; do
if [ -f "$file" ]; then
file_name=$(basename $file)
echo "$file_name exists"
lang=$(basename $file | sed 's/README_//; s/.md//')
push_command="./$file_name:application/vnd.module.wasm.doc.v1.$lang+markdown $push_command "
fi
done
echo "PUSH_COMMAND=\"$push_command\"" >> $GITHUB_ENV
- name: Run a wasm-go-builder
env:
PLUGIN_NAME: ${{ env.PLUGIN_NAME }}
BUILDER_IMAGE: higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go${{ env.GO_VERSION }}-tinygo${{ env.TINYGO_VERSION }}-oras${{ env.ORAS_VERSION }}
run: |
docker run -itd --name builder -v ${{ github.workspace }}:/workspace -e PLUGIN_NAME=${{ env.PLUGIN_NAME }} --rm ${{ env.BUILDER_IMAGE }} /bin/bash
- name: Build Image and Push
run: |
push_command=${{ env.PUSH_COMMAND }}
push_command=${push_command#\"}
push_command=${push_command%\"} # 删除PUSH_COMMAND中的双引号确保oras push正常解析
target_image="${{ env.IMAGE_REGISTRY_SERVICE }}/${{ env.IMAGE_REPOSITORY}}/${{ env.PLUGIN_NAME }}:${{ env.VERSION }}"
echo "TargetImage=${target_image}"
cd ${{ github.workspace }}/plugins/wasm-go/extensions/${PLUGIN_NAME}
if [ -f ./.buildrc ]; then
echo 'Found .buildrc file, sourcing it...'
. ./.buildrc
else
echo '.buildrc file not found'
fi
echo "EXTRA_TAGS=${EXTRA_TAGS}"
command="
set -e
cd /workspace/plugins/wasm-go/extensions/${PLUGIN_NAME}
go mod tidy
tinygo build -o ./plugin.wasm -scheduler=none -target=wasi -gc=custom -tags=\"custommalloc nottinygc_finalizer ${EXTRA_TAGS}\" .
tar czvf plugin.tar.gz plugin.wasm
echo ${{ secrets.REGISTRY_PASSWORD }} | oras login -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin ${{ env.IMAGE_REGISTRY_SERVICE }}
oras push ${target_image} ${push_command}
"
docker exec builder bash -c "$command"

View File

@@ -17,8 +17,8 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: 1.19
# There are too many lint errors in current code bases # There are too many lint errors in current code bases
@@ -30,9 +30,9 @@ jobs:
strategy: strategy:
matrix: matrix:
# TODO(Xunzhuo): Enable C WASM Filters in CI # TODO(Xunzhuo): Enable C WASM Filters in CI
wasmPluginType: [ GO ] wasmPluginType: [ GO, RUST ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Free Up GitHub Actions Ubuntu Runner Disk Space 🔧 - name: Free Up GitHub Actions Ubuntu Runner Disk Space 🔧
uses: jlumbroso/free-disk-space@main uses: jlumbroso/free-disk-space@main
@@ -45,12 +45,12 @@ jobs:
swap-storage: true swap-storage: true
- name: "Setup Go" - name: "Setup Go"
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: 1.19
- name: Setup Golang Caches - name: Setup Golang Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
~/.cache/go-build ~/.cache/go-build
@@ -60,7 +60,7 @@ jobs:
${{ runner.os }}-go ${{ runner.os }}-go
- name: Setup Submodule Caches - name: Setup Submodule Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
.git/modules .git/modules
@@ -81,4 +81,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ higress-wasmplugin-test ] needs: [ higress-wasmplugin-test ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4

View File

@@ -10,8 +10,8 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-go@v3 - uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: 1.19
# There are too many lint errors in current code bases # There are too many lint errors in current code bases
@@ -21,10 +21,10 @@ jobs:
coverage-test: coverage-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Golang Caches - name: Setup Golang Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
~/.cache/go-build ~/.cache/go-build
@@ -33,7 +33,7 @@ jobs:
restore-keys: ${{ runner.os }}-go restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches - name: Setup Submodule Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
.git/modules .git/modules
@@ -46,7 +46,7 @@ jobs:
- name: Run Coverage Tests - name: Run Coverage Tests
run: GOPROXY="https://proxy.golang.org,direct" make go.test.coverage run: GOPROXY="https://proxy.golang.org,direct" make go.test.coverage
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v4
with: with:
fail_ci_if_error: false fail_ci_if_error: false
files: ./coverage.xml files: ./coverage.xml
@@ -58,17 +58,17 @@ jobs:
needs: [lint,coverage-test] needs: [lint,coverage-test]
steps: steps:
- name: "Checkout ${{ github.ref }}" - name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 2 fetch-depth: 2
- name: "Setup Go" - name: "Setup Go"
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: 1.19
- name: Setup Golang Caches - name: Setup Golang Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
~/.cache/go-build ~/.cache/go-build
@@ -77,7 +77,7 @@ jobs:
restore-keys: ${{ runner.os }}-go restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches - name: Setup Submodule Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
.git/modules .git/modules
@@ -90,7 +90,7 @@ jobs:
run: GOPROXY="https://proxy.golang.org,direct" make build run: GOPROXY="https://proxy.golang.org,direct" make build
- name: Upload Higress Binary - name: Upload Higress Binary
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: higress name: higress
path: out/ path: out/
@@ -108,12 +108,12 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: "Setup Go" - name: "Setup Go"
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: 1.19
- name: Setup Golang Caches - name: Setup Golang Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
~/.cache/go-build ~/.cache/go-build
@@ -123,7 +123,7 @@ jobs:
${{ runner.os }}-go ${{ runner.os }}-go
- name: Setup Submodule Caches - name: Setup Submodule Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
.git/modules .git/modules
@@ -139,4 +139,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [higress-conformance-test,gateway-conformance-test] needs: [higress-conformance-test,gateway-conformance-test]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4

View File

@@ -16,7 +16,7 @@ jobs:
CONTROLLER_IMAGE_NAME: ${{ vars.CONTROLLER_IMAGE_NAME || 'higress/higress' }} CONTROLLER_IMAGE_NAME: ${{ vars.CONTROLLER_IMAGE_NAME || 'higress/higress' }}
steps: steps:
- name: "Checkout ${{ github.ref }}" - name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 1 fetch-depth: 1
@@ -31,12 +31,12 @@ jobs:
swap-storage: true swap-storage: true
- name: "Setup Go" - name: "Setup Go"
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: 1.19
- name: Setup Golang Caches - name: Setup Golang Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
~/.cache/go-build ~/.cache/go-build
@@ -45,7 +45,7 @@ jobs:
restore-keys: ${{ runner.os }}-go restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches - name: Setup Submodule Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
envoy envoy
@@ -56,7 +56,7 @@ jobs:
- name: Calculate Docker metadata - name: Calculate Docker metadata
id: docker-meta id: docker-meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v5
with: with:
images: | images: |
${{ env.CONTROLLER_IMAGE_REGISTRY }}/${{ env.CONTROLLER_IMAGE_NAME }} ${{ env.CONTROLLER_IMAGE_REGISTRY }}/${{ env.CONTROLLER_IMAGE_NAME }}
@@ -67,7 +67,7 @@ jobs:
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Login to Docker Registry - name: Login to Docker Registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.CONTROLLER_IMAGE_REGISTRY }} registry: ${{ env.CONTROLLER_IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}
@@ -92,7 +92,7 @@ jobs:
PILOT_IMAGE_NAME: ${{ vars.PILOT_IMAGE_NAME || 'higress/pilot' }} PILOT_IMAGE_NAME: ${{ vars.PILOT_IMAGE_NAME || 'higress/pilot' }}
steps: steps:
- name: "Checkout ${{ github.ref }}" - name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 1 fetch-depth: 1
@@ -107,12 +107,12 @@ jobs:
swap-storage: true swap-storage: true
- name: "Setup Go" - name: "Setup Go"
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: 1.19
- name: Setup Golang Caches - name: Setup Golang Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
~/.cache/go-build ~/.cache/go-build
@@ -121,7 +121,7 @@ jobs:
restore-keys: ${{ runner.os }}-go restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches - name: Setup Submodule Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
envoy envoy
@@ -132,7 +132,7 @@ jobs:
- name: Calculate Docker metadata - name: Calculate Docker metadata
id: docker-meta id: docker-meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v5
with: with:
images: | images: |
${{ env.PILOT_IMAGE_REGISTRY }}/${{ env.PILOT_IMAGE_NAME }} ${{ env.PILOT_IMAGE_REGISTRY }}/${{ env.PILOT_IMAGE_NAME }}
@@ -143,7 +143,7 @@ jobs:
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Login to Docker Registry - name: Login to Docker Registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.PILOT_IMAGE_REGISTRY }} registry: ${{ env.PILOT_IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}
@@ -169,7 +169,7 @@ jobs:
GATEWAY_IMAGE_NAME: ${{ vars.GATEWAY_IMAGE_NAME || 'higress/gateway' }} GATEWAY_IMAGE_NAME: ${{ vars.GATEWAY_IMAGE_NAME || 'higress/gateway' }}
steps: steps:
- name: "Checkout ${{ github.ref }}" - name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 1 fetch-depth: 1
@@ -184,12 +184,12 @@ jobs:
swap-storage: true swap-storage: true
- name: "Setup Go" - name: "Setup Go"
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: 1.19
- name: Setup Golang Caches - name: Setup Golang Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
~/.cache/go-build ~/.cache/go-build
@@ -198,7 +198,7 @@ jobs:
restore-keys: ${{ runner.os }}-go restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches - name: Setup Submodule Caches
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: |- path: |-
envoy envoy
@@ -209,7 +209,7 @@ jobs:
- name: Calculate Docker metadata - name: Calculate Docker metadata
id: docker-meta id: docker-meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v5
with: with:
images: | images: |
${{ env.GATEWAY_IMAGE_REGISTRY }}/${{ env.GATEWAY_IMAGE_NAME }} ${{ env.GATEWAY_IMAGE_REGISTRY }}/${{ env.GATEWAY_IMAGE_NAME }}
@@ -220,7 +220,7 @@ jobs:
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Login to Docker Registry - name: Login to Docker Registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ${{ env.GATEWAY_IMAGE_REGISTRY }} registry: ${{ env.GATEWAY_IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}

View File

@@ -34,11 +34,11 @@ jobs:
steps: steps:
# step 1 # step 1
- name: "Checkout repository" - name: "Checkout repository"
uses: actions/checkout@v2 uses: actions/checkout@v4
# step 2: Initializes the CodeQL tools for scanning. # step 2: Initializes the CodeQL tools for scanning.
- name: "Initialize CodeQL" - name: "Initialize CodeQL"
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -50,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: "Autobuild" - name: "Autobuild"
uses: github/codeql-action/autobuild@v1 uses: github/codeql-action/autobuild@v2
# step 4 # step 4
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
@@ -66,4 +66,4 @@ jobs:
# step 5 # step 5
- name: "Perform CodeQL Analysis" - name: "Perform CodeQL Analysis"
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
# Step 1 # Step 1
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
# Step 2 # Step 2
- id: package - id: package
name: Prepare Standalone Package name: Prepare Standalone Package

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
# Step 1 # Step 1
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
# Step 2 # Step 2
- name: Download Helm Charts Index - name: Download Helm Charts Index
uses: doggycool/ossutil-github-action@master uses: doggycool/ossutil-github-action@master

View File

@@ -9,7 +9,7 @@ jobs:
latest-release: latest-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Build hgctl latest multiarch binaries - name: Build hgctl latest multiarch binaries
run: | run: |
@@ -46,7 +46,7 @@ jobs:
GITHUB_REPOSITORY: ${{ github.repository_owner }}/${{ github.event.repository.name }} GITHUB_REPOSITORY: ${{ github.repository_owner }}/${{ github.event.repository.name }}
- name: Recreate the Latest Release and Tag - name: Recreate the Latest Release and Tag
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
draft: false draft: false
prerelease: true prerelease: true

View File

@@ -10,7 +10,7 @@ jobs:
steps: steps:
# step 1 # step 1
- name: Checkout - name: Checkout
uses: actions/checkout@v2.4.0 uses: actions/checkout@v4
# step 2 # step 2
- name: Check License Header - name: Check License Header
uses: apache/skywalking-eyes/header@25edfc2fd8d52fb266653fb5f6c42da633d85c07 uses: apache/skywalking-eyes/header@25edfc2fd8d52fb266653fb5f6c42da633d85c07
@@ -24,4 +24,4 @@ jobs:
with: with:
log: info log: info
config: .licenserc.yaml config: .licenserc.yaml
mode: check mode: check

View File

@@ -12,7 +12,7 @@ jobs:
env: env:
HGCTL_VERSION: ${{github.ref_name}} HGCTL_VERSION: ${{github.ref_name}}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Build hgctl latest multiarch binaries - name: Build hgctl latest multiarch binaries
run: | run: |
@@ -25,7 +25,7 @@ jobs:
zip -q -r hgctl_${{ env.HGCTL_VERSION }}_windows_arm64.zip out/windows_arm64/ zip -q -r hgctl_${{ env.HGCTL_VERSION }}_windows_arm64.zip out/windows_arm64/
- name: Upload hgctl packages to the GitHub release - name: Upload hgctl packages to the GitHub release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
files: | files: |
@@ -34,4 +34,4 @@ jobs:
hgctl_${{ env.HGCTL_VERSION }}_darwin_amd64.tar.gz hgctl_${{ env.HGCTL_VERSION }}_darwin_amd64.tar.gz
hgctl_${{ env.HGCTL_VERSION }}_darwin_arm64.tar.gz hgctl_${{ env.HGCTL_VERSION }}_darwin_arm64.tar.gz
hgctl_${{ env.HGCTL_VERSION }}_windows_amd64.zip hgctl_${{ env.HGCTL_VERSION }}_windows_amd64.zip
hgctl_${{ env.HGCTL_VERSION }}_windows_arm64.zip hgctl_${{ env.HGCTL_VERSION }}_windows_arm64.zip

View File

@@ -138,11 +138,11 @@ export ENVOY_TAR_PATH:=/home/package/envoy.tar.gz
external/package/envoy-amd64.tar.gz: external/package/envoy-amd64.tar.gz:
# cd external/proxy; BUILD_WITH_CONTAINER=1 make test_release # 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.4.0/envoy-symbol-amd64.tar.gz" cd external/package; wget -O envoy-amd64.tar.gz "https://github.com/alibaba/higress/releases/download/v1.4.1/envoy-symbol-amd64.tar.gz"
external/package/envoy-arm64.tar.gz: external/package/envoy-arm64.tar.gz:
# cd external/proxy; BUILD_WITH_CONTAINER=1 make test_release # 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.4.0/envoy-symbol-arm64.tar.gz" cd external/package; wget -O envoy-arm64.tar.gz "https://github.com/alibaba/higress/releases/download/v1.4.1/envoy-symbol-arm64.tar.gz"
build-pilot: 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 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 cd helm/higress; helm dependency build
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true' helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
ENVOY_LATEST_IMAGE_TAG ?= sha-93966bf ENVOY_LATEST_IMAGE_TAG ?= sha-63539ca
ISTIO_LATEST_IMAGE_TAG ?= sha-b00f79f ISTIO_LATEST_IMAGE_TAG ?= sha-63539ca
install-dev: pre-install 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' helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'

View File

@@ -1 +1 @@
v1.4.1 v1.4.2

View File

@@ -0,0 +1,13 @@
diff --git a/source/common/http/headers.h b/source/common/http/headers.h
index a7a8a3393e..6af4a2852d 100644
--- a/source/common/http/headers.h
+++ b/source/common/http/headers.h
@@ -123,7 +123,7 @@ public:
const LowerCaseString TriCostTime{"req-cost-time"};
const LowerCaseString TriStartTime{"req-start-time"};
const LowerCaseString TriRespStartTime{"resp-start-time"};
- const LowerCaseString EnvoyOriginalHost{"original-host"};
+ const LowerCaseString EnvoyOriginalHost{"x-envoy-original-host"};
const LowerCaseString HigressOriginalService{"x-higress-original-service"};
} AliExtendedValues;
#endif

View File

@@ -0,0 +1,43 @@
diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc
index 9642d8abd3..410baa856f 100644
--- a/source/extensions/common/wasm/context.cc
+++ b/source/extensions/common/wasm/context.cc
@@ -62,6 +62,21 @@ constexpr absl::string_view CelStateKeyPrefix = "wasm.";
#if defined(ALIMESH)
constexpr std::string_view ClearRouteCacheKey = "clear_route_cache";
constexpr std::string_view DisableClearRouteCache = "off";
+constexpr std::string_view SetDecoderBufferLimit = "set_decoder_buffer_limit";
+constexpr std::string_view SetEncoderBufferLimit = "set_encoder_buffer_limit";
+
+bool stringViewToUint32(std::string_view str, uint32_t& out_value) {
+ try {
+ unsigned long temp = std::stoul(std::string(str));
+ if (temp <= std::numeric_limits<uint32_t>::max()) {
+ out_value = static_cast<uint32_t>(temp);
+ return true;
+ }
+ } catch (const std::exception& e) {
+ ENVOY_LOG_MISC(critical, "stringToUint exception '{}'", e.what());
+ }
+ return false;
+}
#endif
using HashPolicy = envoy::config::route::v3::RouteAction::HashPolicy;
@@ -1280,6 +1295,16 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) {
} else {
disable_clear_route_cache_ = false;
}
+ } else if (path == SetDecoderBufferLimit && decoder_callbacks_) {
+ uint32_t buffer_limit;
+ if (stringViewToUint32(value, buffer_limit)) {
+ decoder_callbacks_->setDecoderBufferLimit(buffer_limit);
+ }
+ } else if (path == SetEncoderBufferLimit && encoder_callbacks_) {
+ uint32_t buffer_limit;
+ if (stringViewToUint32(value, buffer_limit)) {
+ encoder_callbacks_->setEncoderBufferLimit(buffer_limit);
+ }
}
#endif
if (!state->setValue(toAbslStringView(value))) {

View File

@@ -0,0 +1,106 @@
diff --git a/envoy/stream_info/stream_info.h b/envoy/stream_info/stream_info.h
index c6d82db4f4..09717673b0 100644
--- a/envoy/stream_info/stream_info.h
+++ b/envoy/stream_info/stream_info.h
@@ -613,7 +613,21 @@ public:
* @return the number of times the request was attempted upstream, absl::nullopt if the request
* was never attempted upstream.
*/
+
virtual absl::optional<uint32_t> attemptCount() const PURE;
+
+#ifdef ALIMESH
+ /**
+ * @param key the filter state key set by wasm filter.
+ * @param value the filter state value set by wasm filter.
+ */
+ virtual void setCustomSpanTag(const std::string& key, const std::string& value) PURE;
+
+ /**
+ * @return the key-value map of filter states set by wasm filter.
+ */
+ virtual const std::unordered_map<std::string, std::string>& getCustomSpanTagMap() const PURE;
+#endif
};
} // namespace StreamInfo
diff --git a/source/common/stream_info/stream_info_impl.h b/source/common/stream_info/stream_info_impl.h
index 6ce2afe773..d5e7a80b37 100644
--- a/source/common/stream_info/stream_info_impl.h
+++ b/source/common/stream_info/stream_info_impl.h
@@ -291,6 +291,20 @@ struct StreamInfoImpl : public StreamInfo {
absl::optional<uint32_t> attemptCount() const override { return attempt_count_; }
+#ifdef ALIMESH
+ void setCustomSpanTag(const std::string& key, const std::string& value) override {
+ auto it = custom_span_tags_.find(key);
+ if (it != custom_span_tags_.end()) {
+ it->second = value;
+ } else {
+ custom_span_tags_.emplace(key, value);
+ }
+ }
+
+ const std::unordered_map<std::string, std::string>& getCustomSpanTagMap() const override {
+ return custom_span_tags_;
+ }
+#endif
TimeSource& time_source_;
const SystemTime start_time_;
const MonotonicTime start_time_monotonic_;
@@ -350,6 +364,9 @@ private:
absl::optional<Upstream::ClusterInfoConstSharedPtr> upstream_cluster_info_;
std::string filter_chain_name_;
Tracing::Reason trace_reason_;
+#ifdef ALIMESH
+ std::unordered_map<std::string, std::string> custom_span_tags_;
+#endif
};
} // namespace StreamInfo
diff --git a/source/common/tracing/http_tracer_impl.cc b/source/common/tracing/http_tracer_impl.cc
index e55cf00e0a..f94e9101d7 100644
--- a/source/common/tracing/http_tracer_impl.cc
+++ b/source/common/tracing/http_tracer_impl.cc
@@ -214,6 +214,14 @@ void HttpTracerUtility::setCommonTags(Span& span, const Http::ResponseHeaderMap*
span.setTag(Tracing::Tags::get().Component, Tracing::Tags::get().Proxy);
+#ifdef ALIMESH
+ // Wasm filter state
+ const auto& custom_span_tags = stream_info.getCustomSpanTagMap();
+ for (const auto& it : custom_span_tags) {
+ span.setTag(it.first, it.second);
+ }
+#endif
+
if (nullptr != stream_info.upstreamHost()) {
span.setTag(Tracing::Tags::get().UpstreamCluster, stream_info.upstreamHost()->cluster().name());
span.setTag(Tracing::Tags::get().UpstreamClusterName,
diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc
index 410baa856f..b11ecf1cd6 100644
--- a/source/extensions/common/wasm/context.cc
+++ b/source/extensions/common/wasm/context.cc
@@ -60,6 +60,7 @@ namespace {
constexpr absl::string_view CelStateKeyPrefix = "wasm.";
#if defined(ALIMESH)
+constexpr absl::string_view CustomeTraceSpanTagPrefix = "trace_span_tag.";
constexpr std::string_view ClearRouteCacheKey = "clear_route_cache";
constexpr std::string_view DisableClearRouteCache = "off";
constexpr std::string_view SetDecoderBufferLimit = "set_decoder_buffer_limit";
@@ -1271,6 +1272,13 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) {
if (!stream_info) {
return WasmResult::NotFound;
}
+#ifdef ALIMESH
+ if (absl::StartsWith(absl::string_view{path.data(), path.size()}, CustomeTraceSpanTagPrefix)) {
+ stream_info->setCustomSpanTag(std::string(path.substr(CustomeTraceSpanTagPrefix.size())),
+ std::string(value));
+ return WasmResult::Ok;
+ }
+#endif
std::string key;
absl::StrAppend(&key, CelStateKeyPrefix, toAbslStringView(path));
CelState* state;

341
get_helm.sh Executable file
View File

@@ -0,0 +1,341 @@
#!/usr/bin/env bash
# Copyright The Helm Authors.
#
# 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.
# The install script is based off of the MIT-licensed script from glide,
# the package manager for Go: https://github.com/Masterminds/glide.sh/blob/master/get
: ${BINARY_NAME:="helm"}
: ${USE_SUDO:="true"}
: ${DEBUG:="false"}
: ${VERIFY_CHECKSUM:="true"}
: ${VERIFY_SIGNATURES:="false"}
: ${HELM_INSTALL_DIR:="/usr/local/bin"}
: ${GPG_PUBRING:="pubring.kbx"}
HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)"
HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)"
HAS_OPENSSL="$(type "openssl" &> /dev/null && echo true || echo false)"
HAS_GPG="$(type "gpg" &> /dev/null && echo true || echo false)"
HAS_GIT="$(type "git" &> /dev/null && echo true || echo false)"
# initArch discovers the architecture for this system.
initArch() {
ARCH=$(uname -m)
case $ARCH in
armv5*) ARCH="armv5";;
armv6*) ARCH="armv6";;
armv7*) ARCH="arm";;
aarch64) ARCH="arm64";;
x86) ARCH="386";;
x86_64) ARCH="amd64";;
i686) ARCH="386";;
i386) ARCH="386";;
esac
}
# initOS discovers the operating system for this system.
initOS() {
OS=$(echo `uname`|tr '[:upper:]' '[:lower:]')
case "$OS" in
# Minimalist GNU for Windows
mingw*|cygwin*) OS='windows';;
esac
}
# runs the given command as root (detects if we are root already)
runAsRoot() {
if [ $EUID -ne 0 -a "$USE_SUDO" = "true" ]; then
sudo "${@}"
else
"${@}"
fi
}
# verifySupported checks that the os/arch combination is supported for
# binary builds, as well whether or not necessary tools are present.
verifySupported() {
local supported="darwin-amd64\ndarwin-arm64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64\nwindows-arm64"
if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
echo "No prebuilt binary for ${OS}-${ARCH}."
echo "To build from source, go to https://github.com/helm/helm"
exit 1
fi
if [ "${HAS_CURL}" != "true" ] && [ "${HAS_WGET}" != "true" ]; then
echo "Either curl or wget is required"
exit 1
fi
if [ "${VERIFY_CHECKSUM}" == "true" ] && [ "${HAS_OPENSSL}" != "true" ]; then
echo "In order to verify checksum, openssl must first be installed."
echo "Please install openssl or set VERIFY_CHECKSUM=false in your environment."
exit 1
fi
if [ "${VERIFY_SIGNATURES}" == "true" ]; then
if [ "${HAS_GPG}" != "true" ]; then
echo "In order to verify signatures, gpg must first be installed."
echo "Please install gpg or set VERIFY_SIGNATURES=false in your environment."
exit 1
fi
if [ "${OS}" != "linux" ]; then
echo "Signature verification is currently only supported on Linux."
echo "Please set VERIFY_SIGNATURES=false or verify the signatures manually."
exit 1
fi
fi
if [ "${HAS_GIT}" != "true" ]; then
echo "[WARNING] Could not find git. It is required for plugin installation."
fi
}
# checkDesiredVersion checks if the desired version is available.
checkDesiredVersion() {
if [ "x$DESIRED_VERSION" == "x" ]; then
# Get tag from release URL
local latest_release_url="https://get.helm.sh/helm-latest-version"
local latest_release_response=""
if [ "${HAS_CURL}" == "true" ]; then
latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true )
elif [ "${HAS_WGET}" == "true" ]; then
latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true )
fi
TAG=$( echo "$latest_release_response" | grep '^v[0-9]' )
if [ "x$TAG" == "x" ]; then
printf "Could not retrieve the latest release tag information from %s: %s\n" "${latest_release_url}" "${latest_release_response}"
exit 1
fi
else
TAG=$DESIRED_VERSION
fi
}
# checkHelmInstalledVersion checks which version of helm is installed and
# if it needs to be changed.
checkHelmInstalledVersion() {
if [[ -f "${HELM_INSTALL_DIR}/${BINARY_NAME}" ]]; then
local version=$("${HELM_INSTALL_DIR}/${BINARY_NAME}" version --template="{{ .Version }}")
if [[ "$version" == "$TAG" ]]; then
echo "Helm ${version} is already ${DESIRED_VERSION:-latest}"
return 0
else
echo "Helm ${TAG} is available. Changing from version ${version}."
return 1
fi
else
return 1
fi
}
# downloadFile downloads the latest binary package and also the checksum
# for that binary.
downloadFile() {
HELM_DIST="helm-$TAG-$OS-$ARCH.tar.gz"
DOWNLOAD_URL="https://get.helm.sh/$HELM_DIST"
CHECKSUM_URL="$DOWNLOAD_URL.sha256"
HELM_TMP_ROOT="$(mktemp -dt helm-installer-XXXXXX)"
HELM_TMP_FILE="$HELM_TMP_ROOT/$HELM_DIST"
HELM_SUM_FILE="$HELM_TMP_ROOT/$HELM_DIST.sha256"
echo "Downloading $DOWNLOAD_URL"
if [ "${HAS_CURL}" == "true" ]; then
curl -SsL "$CHECKSUM_URL" -o "$HELM_SUM_FILE"
curl -SsL "$DOWNLOAD_URL" -o "$HELM_TMP_FILE"
elif [ "${HAS_WGET}" == "true" ]; then
wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL"
wget -q -O "$HELM_TMP_FILE" "$DOWNLOAD_URL"
fi
}
# verifyFile verifies the SHA256 checksum of the binary package
# and the GPG signatures for both the package and checksum file
# (depending on settings in environment).
verifyFile() {
if [ "${VERIFY_CHECKSUM}" == "true" ]; then
verifyChecksum
fi
if [ "${VERIFY_SIGNATURES}" == "true" ]; then
verifySignatures
fi
}
# installFile installs the Helm binary.
installFile() {
HELM_TMP="$HELM_TMP_ROOT/$BINARY_NAME"
mkdir -p "$HELM_TMP"
tar xf "$HELM_TMP_FILE" -C "$HELM_TMP"
HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/helm"
echo "Preparing to install $BINARY_NAME into ${HELM_INSTALL_DIR}"
runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR/$BINARY_NAME"
echo "$BINARY_NAME installed into $HELM_INSTALL_DIR/$BINARY_NAME"
}
# verifyChecksum verifies the SHA256 checksum of the binary package.
verifyChecksum() {
printf "Verifying checksum... "
local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}')
local expected_sum=$(cat ${HELM_SUM_FILE})
if [ "$sum" != "$expected_sum" ]; then
echo "SHA sum of ${HELM_TMP_FILE} does not match. Aborting."
exit 1
fi
echo "Done."
}
# verifySignatures obtains the latest KEYS file from GitHub main branch
# as well as the signature .asc files from the specific GitHub release,
# then verifies that the release artifacts were signed by a maintainer's key.
verifySignatures() {
printf "Verifying signatures... "
local keys_filename="KEYS"
local github_keys_url="https://raw.githubusercontent.com/helm/helm/main/${keys_filename}"
if [ "${HAS_CURL}" == "true" ]; then
curl -SsL "${github_keys_url}" -o "${HELM_TMP_ROOT}/${keys_filename}"
elif [ "${HAS_WGET}" == "true" ]; then
wget -q -O "${HELM_TMP_ROOT}/${keys_filename}" "${github_keys_url}"
fi
local gpg_keyring="${HELM_TMP_ROOT}/keyring.gpg"
local gpg_homedir="${HELM_TMP_ROOT}/gnupg"
mkdir -p -m 0700 "${gpg_homedir}"
local gpg_stderr_device="/dev/null"
if [ "${DEBUG}" == "true" ]; then
gpg_stderr_device="/dev/stderr"
fi
gpg --batch --quiet --homedir="${gpg_homedir}" --import "${HELM_TMP_ROOT}/${keys_filename}" 2> "${gpg_stderr_device}"
gpg --batch --no-default-keyring --keyring "${gpg_homedir}/${GPG_PUBRING}" --export > "${gpg_keyring}"
local github_release_url="https://github.com/helm/helm/releases/download/${TAG}"
if [ "${HAS_CURL}" == "true" ]; then
curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc"
curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc"
elif [ "${HAS_WGET}" == "true" ]; then
wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc"
wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc"
fi
local error_text="If you think this might be a potential security issue,"
error_text="${error_text}\nplease see here: https://github.com/helm/community/blob/master/SECURITY.md"
local num_goodlines_sha=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)')
if [[ ${num_goodlines_sha} -lt 2 ]]; then
echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256!"
echo -e "${error_text}"
exit 1
fi
local num_goodlines_tar=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)')
if [[ ${num_goodlines_tar} -lt 2 ]]; then
echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz!"
echo -e "${error_text}"
exit 1
fi
echo "Done."
}
# fail_trap is executed if an error occurs.
fail_trap() {
result=$?
if [ "$result" != "0" ]; then
if [[ -n "$INPUT_ARGUMENTS" ]]; then
echo "Failed to install $BINARY_NAME with the arguments provided: $INPUT_ARGUMENTS"
help
else
echo "Failed to install $BINARY_NAME"
fi
echo -e "\tFor support, go to https://github.com/helm/helm."
fi
cleanup
exit $result
}
# testVersion tests the installed client to make sure it is working.
testVersion() {
set +e
HELM="$(command -v $BINARY_NAME)"
if [ "$?" = "1" ]; then
echo "$BINARY_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?'
exit 1
fi
set -e
}
# help provides possible cli installation arguments
help () {
echo "Accepted cli arguments are:"
echo -e "\t[--help|-h ] ->> prints this help"
echo -e "\t[--version|-v <desired_version>] . When not defined it fetches the latest release from GitHub"
echo -e "\te.g. --version v3.0.0 or -v canary"
echo -e "\t[--no-sudo] ->> install without sudo"
}
# cleanup temporary files to avoid https://github.com/helm/helm/issues/2977
cleanup() {
if [[ -d "${HELM_TMP_ROOT:-}" ]]; then
rm -rf "$HELM_TMP_ROOT"
fi
}
# Execution
#Stop execution on any error
trap "fail_trap" EXIT
set -e
# Set debug if desired
if [ "${DEBUG}" == "true" ]; then
set -x
fi
# Parsing input arguments (if any)
export INPUT_ARGUMENTS="${@}"
set -u
while [[ $# -gt 0 ]]; do
case $1 in
'--version'|-v)
shift
if [[ $# -ne 0 ]]; then
export DESIRED_VERSION="${1}"
if [[ "$1" != "v"* ]]; then
echo "Expected version arg ('${DESIRED_VERSION}') to begin with 'v', fixing..."
export DESIRED_VERSION="v${1}"
fi
else
echo -e "Please provide the desired version. e.g. --version v3.0.0 or -v canary"
exit 0
fi
;;
'--no-sudo')
USE_SUDO="false"
;;
'--help'|-h)
help
exit 0
;;
*) exit 1
;;
esac
shift
done
set +u
initArch
initOS
verifySupported
checkDesiredVersion
if ! checkHelmInstalledVersion; then
downloadFile
verifyFile
installFile
fi
testVersion
cleanup

4
go.mod
View File

@@ -255,7 +255,6 @@ require (
go.opentelemetry.io/proto/otlp v0.12.0 // indirect go.opentelemetry.io/proto/otlp v0.12.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/multierr v1.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/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/mod v0.11.0 // indirect golang.org/x/mod v0.11.0 // indirect
@@ -304,7 +303,7 @@ replace istio.io/client-go => ./external/client-go
replace istio.io/istio => ./external/istio replace istio.io/istio => ./external/istio
replace github.com/caddyserver/certmagic => github.com/2456868764/certmagic v1.0.1 replace github.com/caddyserver/certmagic => github.com/2456868764/certmagic v1.0.2
require ( require (
github.com/caddyserver/certmagic v0.20.0 github.com/caddyserver/certmagic v0.20.0
@@ -313,6 +312,7 @@ require (
github.com/kylelemons/godebug v1.1.0 github.com/kylelemons/godebug v1.1.0
github.com/mholt/acmez v1.2.0 github.com/mholt/acmez v1.2.0
github.com/tidwall/gjson v1.17.0 github.com/tidwall/gjson v1.17.0
go.uber.org/zap v1.24.0
golang.org/x/net v0.17.0 golang.org/x/net v0.17.0
helm.sh/helm/v3 v3.7.1 helm.sh/helm/v3 v3.7.1
k8s.io/apiextensions-apiserver v0.25.4 k8s.io/apiextensions-apiserver v0.25.4

4
go.sum
View File

@@ -61,8 +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/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= 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= 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.2 h1:xYoN4z6seONwT85llWXZcASvQME8TOSiSWQvLJsGGsE=
github.com/2456868764/certmagic v1.0.1/go.mod h1:LOn81EQYMPajdew6Ln6SVdHPxPqPv6jwsUg92kiNlcQ= github.com/2456868764/certmagic v1.0.2/go.mod h1:LOn81EQYMPajdew6Ln6SVdHPxPqPv6jwsUg92kiNlcQ=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210929163055-e81b3f25be97/go.mod h1:WpB7kf89yJUETZxQnP1kgYPNwlT2jjdDYUCoxVggM3g= 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 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=

View File

@@ -1,5 +1,5 @@
apiVersion: v2 apiVersion: v2
appVersion: 1.4.1 appVersion: 1.4.2
description: Helm chart for deploying higress gateways description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/ home: http://higress.io/
@@ -10,4 +10,4 @@ name: higress-core
sources: sources:
- http://github.com/alibaba/higress - http://github.com/alibaba/higress
type: application type: application
version: 1.4.1 version: 1.4.2

View File

@@ -88,7 +88,7 @@
{{- if .Values.global.enableHigressIstio }} {{- if .Values.global.enableHigressIstio }}
discoveryAddress: {{ printf "istiod.%s.svc" .Values.global.istioNamespace }}:15012 discoveryAddress: {{ printf "istiod.%s.svc" .Values.global.istioNamespace }}:15012
{{- else }} {{- else }}
discoveryAddress: higress-controller.{{.Release.Namespace}}.svc:15012 discoveryAddress: {{ include "controller.name" . }}.{{.Release.Namespace}}.svc:15012
{{- end }} {{- end }}
{{- end }} {{- end }}
proxyStatsMatcher: proxyStatsMatcher:

View File

@@ -9,7 +9,7 @@ rules:
# ingress controller # ingress controller
- apiGroups: ["extensions", "networking.k8s.io"] - apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses"] resources: ["ingresses"]
verbs: ["get", "list", "watch"] verbs: ["create", "get", "list", "watch", "update", "delete", "patch"]
- apiGroups: ["extensions", "networking.k8s.io"] - apiGroups: ["extensions", "networking.k8s.io"]
resources: ["ingresses/status"] resources: ["ingresses/status"]
verbs: ["*"] verbs: ["*"]
@@ -36,7 +36,7 @@ rules:
# Needed for multicluster secret reading, possibly ingress certs in the future # Needed for multicluster secret reading, possibly ingress certs in the future
- apiGroups: [""] - apiGroups: [""]
resources: ["secrets"] resources: ["secrets"]
verbs: ["get", "watch", "list"] verbs: ["get", "watch", "list", "create", "update", "delete", "patch"]
- apiGroups: ["networking.higress.io"] - apiGroups: ["networking.higress.io"]
resources: ["mcpbridges"] resources: ["mcpbridges"]
@@ -61,12 +61,12 @@ rules:
# discovery and routing # discovery and routing
- apiGroups: [""] - apiGroups: [""]
resources: ["pods", "nodes", "services", "namespaces", "endpoints"] resources: ["pods", "nodes", "services", "namespaces", "endpoints", "deployments"]
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"] - apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"] resources: ["endpointslices"]
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
# Istiod and bootstrap. # Istiod and bootstrap.
- apiGroups: ["certificates.k8s.io"] - apiGroups: ["certificates.k8s.io"]
resources: resources:
@@ -100,7 +100,7 @@ rules:
- apiGroups: ["multicluster.x-k8s.io"] - apiGroups: ["multicluster.x-k8s.io"]
resources: ["serviceimports"] resources: ["serviceimports"]
verbs: ["get", "watch", "list"] verbs: ["get", "watch", "list"]
# sidecar injection controller # sidecar injection controller
- apiGroups: ["admissionregistration.k8s.io"] - apiGroups: ["admissionregistration.k8s.io"]
resources: ["mutatingwebhookconfigurations"] resources: ["mutatingwebhookconfigurations"]

View File

@@ -26,9 +26,70 @@ spec:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
serviceAccountName: {{ include "controller.serviceAccountName" . }} serviceAccountName: {{ include "controller.serviceAccountName" . }}
{{- if .Values.global.priorityClassName }}
priorityClassName: "{{ .Values.global.priorityClassName }}"
{{- end }}
securityContext: securityContext:
{{- toYaml .Values.controller.podSecurityContext | nindent 8 }} {{- toYaml .Values.controller.podSecurityContext | nindent 8 }}
containers: containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.controller.securityContext | nindent 12 }}
image: "{{ .Values.controller.hub | default .Values.global.hub }}/{{ .Values.controller.image | default "higress" }}:{{ .Values.controller.tag | default .Chart.AppVersion }}"
args:
- "serve"
- --gatewaySelectorKey=higress
- --gatewaySelectorValue={{ .Release.Namespace }}-{{ include "gateway.name" . }}
- --gatewayHttpPort={{ .Values.gateway.httpPort }}
- --gatewayHttpsPort={{ .Values.gateway.httpsPort }}
{{- if not .Values.global.enableStatus }}
- --enableStatus={{ .Values.global.enableStatus }}
{{- end }}
- --ingressClass={{ .Values.global.ingressClass }}
{{- if .Values.global.watchNamespace }}
- --watchNamespace={{ .Values.global.watchNamespace }}
{{- end }}
- --enableAutomaticHttps={{ .Values.controller.automaticHttps.enabled }}
- --automaticHttpsEmail={{ .Values.controller.automaticHttps.email }}
env:
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: SERVICE_ACCOUNT
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.serviceAccountName
- name: DOMAIN_SUFFIX
value: {{ .Values.global.proxy.clusterDomain }}
{{- if .Values.controller.env }}
{{- range $key, $val := .Values.controller.env }}
- name: {{ $key }}
value: "{{ $val }}"
{{- end }}
{{- end }}
ports:
{{- range $idx, $port := .Values.controller.ports }}
- name: {{ $port.name }}
containerPort: {{ $port.port }}
protocol: {{ $port.protocol }}
{{- end }}
readinessProbe:
{{- toYaml .Values.controller.probe | nindent 12 }}
{{- if not (or .Values.global.local .Values.global.kind) }}
resources:
{{- toYaml .Values.controller.resources | nindent 12 }}
{{- end }}
volumeMounts:
- name: log
mountPath: /var/log
{{- if not .Values.global.enableHigressIstio }} {{- if not .Values.global.enableHigressIstio }}
- name: discovery - name: discovery
image: "{{ .Values.pilot.hub | default .Values.global.hub }}/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Chart.AppVersion }}" image: "{{ .Values.pilot.hub | default .Values.global.hub }}/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Chart.AppVersion }}"
@@ -191,64 +252,6 @@ spec:
mountPath: /cacerts mountPath: /cacerts
{{- end }} {{- end }}
{{- end }} {{- end }}
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.controller.securityContext | nindent 12 }}
image: "{{ .Values.controller.hub | default .Values.global.hub }}/{{ .Values.controller.image | default "higress" }}:{{ .Values.controller.tag | default .Chart.AppVersion }}"
args:
- "serve"
- --gatewaySelectorKey=higress
- --gatewaySelectorValue={{ .Release.Namespace }}-{{ include "gateway.name" . }}
- --gatewayHttpPort={{ .Values.gateway.httpPort }}
- --gatewayHttpsPort={{ .Values.gateway.httpsPort }}
{{- if not .Values.global.enableStatus }}
- --enableStatus={{ .Values.global.enableStatus }}
{{- end }}
- --ingressClass={{ .Values.global.ingressClass }}
{{- if .Values.global.watchNamespace }}
- --watchNamespace={{ .Values.global.watchNamespace }}
{{- end }}
- --enableAutomaticHttps={{ .Values.controller.automaticHttps.enabled }}
- --automaticHttpsEmail={{ .Values.controller.automaticHttps.email }}
env:
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: SERVICE_ACCOUNT
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.serviceAccountName
- name: DOMAIN_SUFFIX
value: {{ .Values.global.proxy.clusterDomain }}
{{- if .Values.controller.env }}
{{- range $key, $val := .Values.controller.env }}
- name: {{ $key }}
value: "{{ $val }}"
{{- end }}
{{- end }}
ports:
{{- range $idx, $port := .Values.controller.ports }}
- name: {{ $port.name }}
containerPort: {{ $port.port }}
protocol: {{ $port.protocol }}
{{- end }}
readinessProbe:
{{- toYaml .Values.controller.probe | nindent 12 }}
{{- if not (or .Values.global.local .Values.global.kind) }}
resources:
{{- toYaml .Values.controller.resources | nindent 12 }}
{{- end }}
volumeMounts:
- name: log
mountPath: /var/log
{{- with .Values.controller.nodeSelector }} {{- with .Values.controller.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}

View File

@@ -0,0 +1,332 @@
{{- if eq .Values.gateway.kind "DaemonSet" -}}
{{- $o11y := .Values.global.o11y }}
{{- $unprivilegedPortSupported := true }}
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
{{- $kernelVersion := $node.status.nodeInfo.kernelVersion }}
{{- if $kernelVersion }}
{{- $kernelVersion = regexFind "^(\\d+\\.\\d+\\.\\d+)" $kernelVersion }}
{{- if and $kernelVersion (semverCompare "<4.11.0" $kernelVersion) }}
{{- $unprivilegedPortSupported = false }}
{{- end }}
{{- end }}
{{- end -}}
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: {{ include "gateway.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "gateway.labels" . | nindent 4}}
annotations:
{{- .Values.gateway.annotations | toYaml | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "gateway.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
{{- if .Values.global.enableHigressIstio }}
"enableHigressIstio": "true"
{{- end }}
{{- if .Values.gateway.podAnnotations }}
{{- toYaml .Values.gateway.podAnnotations | nindent 8 }}
{{- end }}
labels:
sidecar.istio.io/inject: "false"
{{- with .Values.gateway.revision }}
istio.io/rev: {{ . }}
{{- end }}
{{- include "gateway.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.gateway.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "gateway.serviceAccountName" . }}
{{- if .Values.global.priorityClassName }}
priorityClassName: "{{ .Values.global.priorityClassName }}"
{{- end }}
securityContext:
{{- if .Values.gateway.securityContext }}
{{- toYaml .Values.gateway.securityContext | nindent 8 }}
{{- else if and $unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
sysctls:
- name: net.ipv4.ip_unprivileged_port_start
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:
- proxy
- router
- --domain
- $(POD_NAMESPACE).svc.cluster.local
- --proxyLogLevel=warning
- --proxyComponentLogLevel=misc:error
- --log_output_level=all:info
- --serviceCluster=higress-gateway
securityContext:
{{- if .Values.gateway.containerSecurityContext }}
{{- toYaml .Values.gateway.containerSecurityContext | nindent 12 }}
{{- else if and $unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
capabilities:
drop:
- 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
{{- else }}
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 0
runAsGroup: 1337
runAsNonRoot: false
allowPrivilegeEscalation: true
{{- end }}
env:
- name: NODE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- name: INSTANCE_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.podIP
- name: HOST_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: SERVICE_ACCOUNT
valueFrom:
fieldRef:
fieldPath: spec.serviceAccountName
- name: PILOT_XDS_SEND_TIMEOUT
value: 60s
- name: PROXY_XDS_VIA_AGENT
value: "true"
- name: ENABLE_INGRESS_GATEWAY_SDS
value: "false"
- name: JWT_POLICY
value: {{ include "controller.jwtPolicy" . }}
- name: ISTIO_META_HTTP10
value: "1"
- name: ISTIO_META_CLUSTER_ID
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
{{- end }}
{{- with .Values.gateway.networkGateway }}
- name: ISTIO_META_REQUESTED_NETWORK_VIEW
value: "{{.}}"
{{- end }}
{{- range $key, $val := .Values.env }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
ports:
- containerPort: 15090
protocol: TCP
name: http-envoy-prom
{{- if or .Values.global.local .Values.global.kind }}
- containerPort: {{ .Values.gateway.httpPort }}
hostPort: {{ .Values.gateway.httpPort }}
name: http
protocol: TCP
- containerPort: {{ .Values.gateway.httpsPort }}
hostPort: {{ .Values.gateway.httpsPort }}
name: https
protocol: TCP
{{- end }}
readinessProbe:
failureThreshold: {{ .Values.gateway.readinessFailureThreshold }}
httpGet:
path: /healthz/ready
port: 15021
scheme: HTTP
initialDelaySeconds: {{ .Values.gateway.readinessInitialDelaySeconds }}
periodSeconds: {{ .Values.gateway.readinessPeriodSeconds }}
successThreshold: {{ .Values.gateway.readinessSuccessThreshold }}
timeoutSeconds: {{ .Values.gateway.readinessTimeoutSeconds }}
{{- if not (or .Values.global.local .Values.global.kind) }}
resources:
{{- toYaml .Values.gateway.resources | nindent 12 }}
{{- end }}
volumeMounts:
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
mountPath: /var/run/secrets/tokens
readOnly: true
{{- end }}
- name: config
mountPath: /etc/istio/config
- name: istio-ca-root-cert
mountPath: /var/run/secrets/istio
- name: istio-data
mountPath: /var/lib/istio/data
- name: podinfo
mountPath: /etc/istio/pod
- name: proxy-socket
mountPath: /etc/istio/proxy
{{- if include "skywalking.enabled" . }}
- mountPath: /etc/istio/custom-bootstrap
name: custom-bootstrap-volume
{{- end }}
{{- if .Values.global.volumeWasmPlugins }}
- 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
{{- end }}
{{- with .Values.gateway.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.gateway.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.gateway.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
projected:
sources:
- serviceAccountToken:
audience: istio-ca
expirationSeconds: 43200
path: istio-token
{{- end }}
- name: istio-ca-root-cert
configMap:
{{- if .Values.global.enableHigressIstio }}
name: istio-ca-root-cert
{{- else }}
name: higress-ca-root-cert
{{- end }}
- name: config
configMap:
name: higress-config
{{- if include "skywalking.enabled" . }}
- configMap:
defaultMode: 420
name: higress-custom-bootstrap
name: custom-bootstrap-volume
{{- end }}
- name: istio-data
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
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.labels
path: labels
- fieldRef:
apiVersion: v1
fieldPath: metadata.annotations
path: annotations
- path: cpu-request
resourceFieldRef:
containerName: higress-gateway
divisor: 1m
resource: requests.cpu
- path: cpu-limit
resourceFieldRef:
containerName: higress-gateway
divisor: 1m
resource: limits.cpu
{{- if .Values.global.volumeWasmPlugins }}
- name: local-wasmplugins-volume
hostPath:
path: /opt/plugins
type: Directory
{{- end }}
{{- end }}

View File

@@ -1,3 +1,4 @@
{{- if eq .Values.gateway.kind "Deployment" -}}
{{- $o11y := .Values.global.o11y }} {{- $o11y := .Values.global.o11y }}
{{- $unprivilegedPortSupported := true }} {{- $unprivilegedPortSupported := true }}
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }} {{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
@@ -58,6 +59,9 @@ spec:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
serviceAccountName: {{ include "gateway.serviceAccountName" . }} serviceAccountName: {{ include "gateway.serviceAccountName" . }}
{{- if .Values.global.priorityClassName }}
priorityClassName: "{{ .Values.global.priorityClassName }}"
{{- end }}
securityContext: securityContext:
{{- if .Values.gateway.securityContext }} {{- if .Values.gateway.securityContext }}
{{- toYaml .Values.gateway.securityContext | nindent 8 }} {{- toYaml .Values.gateway.securityContext | nindent 8 }}
@@ -68,40 +72,6 @@ spec:
value: "0" value: "0"
{{- end }} {{- end }}
containers: 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 - name: higress-gateway
image: "{{ .Values.gateway.hub | default .Values.global.hub }}/{{ .Values.gateway.image | default "gateway" }}:{{ .Values.gateway.tag | default .Chart.AppVersion }}" image: "{{ .Values.gateway.hub | default .Values.global.hub }}/{{ .Values.gateway.image | default "gateway" }}:{{ .Values.gateway.tag | default .Chart.AppVersion }}"
args: args:
@@ -202,6 +172,9 @@ spec:
value: {{ $val | quote }} value: {{ $val | quote }}
{{- end }} {{- end }}
ports: ports:
- containerPort: 15020
protocol: TCP
name: istio-prom
- containerPort: 15090 - containerPort: 15090
protocol: TCP protocol: TCP
name: http-envoy-prom name: http-envoy-prom
@@ -241,7 +214,7 @@ spec:
mountPath: /var/run/secrets/istio mountPath: /var/run/secrets/istio
- name: istio-data - name: istio-data
mountPath: /var/lib/istio/data mountPath: /var/lib/istio/data
- name: podinfo - name: podinfo
mountPath: /etc/istio/pod mountPath: /etc/istio/pod
- name: proxy-socket - name: proxy-socket
mountPath: /etc/istio/proxy mountPath: /etc/istio/proxy
@@ -257,6 +230,40 @@ spec:
- mountPath: /var/log/proxy - mountPath: /var/log/proxy
name: log name: log
{{- end }} {{- end }}
{{- 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 }}
{{- if .Values.gateway.hostNetwork }} {{- if .Values.gateway.hostNetwork }}
hostNetwork: {{ .Values.gateway.hostNetwork }} hostNetwork: {{ .Values.gateway.hostNetwork }}
dnsPolicy: ClusterFirstWithHostNet dnsPolicy: ClusterFirstWithHostNet
@@ -340,3 +347,4 @@ spec:
path: /opt/plugins path: /opt/plugins
type: Directory type: Directory
{{- end }} {{- end }}
{{- end }}

View File

@@ -0,0 +1,6 @@
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: {{ .Values.global.ingressClass }}
spec:
controller: higress.io/higress-controller

View File

@@ -15,6 +15,9 @@ spec:
{{- with .Values.gateway.service.loadBalancerIP }} {{- with .Values.gateway.service.loadBalancerIP }}
loadBalancerIP: "{{ . }}" loadBalancerIP: "{{ . }}"
{{- end }} {{- end }}
{{- with .Values.gateway.service.loadBalancerClass }}
loadBalancerClass: "{{ . }}"
{{- end }}
{{- with .Values.gateway.service.loadBalancerSourceRanges }} {{- with .Values.gateway.service.loadBalancerSourceRanges }}
loadBalancerSourceRanges: loadBalancerSourceRanges:
{{ toYaml . | indent 4 }} {{ toYaml . | indent 4 }}

View File

@@ -343,7 +343,7 @@ global:
enabled: false enabled: false
promtail: promtail:
image: image:
repository: grafana/promtail repository: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/promtail
tag: 2.9.4 tag: 2.9.4
port: 3101 port: 3101
resources: resources:
@@ -396,6 +396,9 @@ gateway:
replicas: 2 replicas: 2
image: gateway image: gateway
# -- Use a `DaemonSet` or `Deployment`
kind: Deployment
# The number of successive failed probes before indicating readiness failure. # The number of successive failed probes before indicating readiness failure.
readinessFailureThreshold: 30 readinessFailureThreshold: 30
@@ -468,6 +471,7 @@ gateway:
targetPort: 443 targetPort: 443
annotations: {} annotations: {}
loadBalancerIP: "" loadBalancerIP: ""
loadBalancerClass: ""
loadBalancerSourceRanges: [] loadBalancerSourceRanges: []
externalTrafficPolicy: "" externalTrafficPolicy: ""

View File

@@ -1,9 +1,9 @@
dependencies: dependencies:
- name: higress-core - name: higress-core
repository: file://../core repository: file://../core
version: 1.4.1 version: 1.4.2
- name: higress-console - name: higress-console
repository: https://higress.io/helm-charts/ repository: https://higress.io/helm-charts/
version: 1.4.1 version: 1.4.2
digest: sha256:de41b8f771e869aef9b83d2334fea5d34492a1c5df37e5aaff383189877cba23 digest: sha256:31b557e55584e589b140ae9b89cfc8b99df91771c7d28465c3a2b06a4f35a192
generated: "2024-06-19T17:10:02.426994+08:00" generated: "2024-07-26T13:53:23.225023+08:00"

View File

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

View File

@@ -391,7 +391,7 @@ func (s *Server) initAutomaticHttps() error {
ServerAddress: s.CertHttpAddress, ServerAddress: s.CertHttpAddress,
Email: s.AutomaticHttpsEmail, Email: s.AutomaticHttpsEmail,
} }
certServer, err := cert.NewServer(s.kubeClient.Kube(), certOption) certServer, err := cert.NewServer(s.kubeClient.Kube(), s.xdsServer, certOption)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -17,10 +17,15 @@ package cert
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"reflect"
"sync" "sync"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/mholt/acmez" "github.com/mholt/acmez"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"istio.io/istio/pilot/pkg/model"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
) )
@@ -28,6 +33,10 @@ const (
EventCertObtained = "cert_obtained" EventCertObtained = "cert_obtained"
) )
var (
cfg *certmagic.Config
)
type CertMgr struct { type CertMgr struct {
cfg *certmagic.Config cfg *certmagic.Config
client kubernetes.Interface client kubernetes.Interface
@@ -39,9 +48,10 @@ type CertMgr struct {
ingressSolver acmez.Solver ingressSolver acmez.Solver
configMgr *ConfigMgr configMgr *ConfigMgr
secretMgr *SecretMgr secretMgr *SecretMgr
XDSUpdater model.XDSUpdater
} }
func InitCertMgr(opts *Option, clientSet kubernetes.Interface, config *Config) (*CertMgr, error) { func InitCertMgr(opts *Option, clientSet kubernetes.Interface, config *Config, XDSUpdater model.XDSUpdater, configMgr *ConfigMgr) (*CertMgr, error) {
CertLog.Infof("certmgr init config: %+v", config) CertLog.Infof("certmgr init config: %+v", config)
// Init certmagic config // Init certmagic config
// First make a pointer to a Cache as we need to reference the same Cache in // First make a pointer to a Cache as we need to reference the same Cache in
@@ -49,21 +59,29 @@ func InitCertMgr(opts *Option, clientSet kubernetes.Interface, config *Config) (
var cache *certmagic.Cache var cache *certmagic.Cache
var storage certmagic.Storage var storage certmagic.Storage
storage, _ = NewConfigmapStorage(opts.Namespace, clientSet) storage, _ = NewConfigmapStorage(opts.Namespace, clientSet)
renewalWindowRatio := float64(config.RenewBeforeDays / RenewMaxDays) renewalWindowRatio := float64(config.RenewBeforeDays) / float64(RenewMaxDays)
logger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.DebugLevel,
))
magicConfig := certmagic.Config{ magicConfig := certmagic.Config{
RenewalWindowRatio: renewalWindowRatio, RenewalWindowRatio: renewalWindowRatio,
Storage: storage, Storage: storage,
Logger: logger,
} }
cache = certmagic.NewCache(certmagic.CacheOptions{ cache = certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) { GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
// Here we use New to get a valid Config associated with the same cache. // 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 // The provided Config is used as a template and will be completed with
// any defaults that are set in the Default config. // any defaults that are set in the Default config.
return certmagic.New(cache, magicConfig), nil return cfg, nil
}, },
Logger: logger,
}) })
// init certmagic // init certmagic
cfg := certmagic.New(cache, magicConfig) cfg = certmagic.New(cache, magicConfig)
// Init certmagic acme // Init certmagic acme
issuer := config.GetIssuer(IssuerTypeLetsencrypt) issuer := config.GetIssuer(IssuerTypeLetsencrypt)
if issuer == nil { if issuer == nil {
@@ -85,7 +103,6 @@ func InitCertMgr(opts *Option, clientSet kubernetes.Interface, config *Config) (
// init issuers // init issuers
cfg.Issuers = []certmagic.Issuer{myACME} cfg.Issuers = []certmagic.Issuer{myACME}
configMgr, _ := NewConfigMgr(opts.Namespace, clientSet)
secretMgr, _ := NewSecretMgr(opts.Namespace, clientSet) secretMgr, _ := NewSecretMgr(opts.Namespace, clientSet)
certMgr := &CertMgr{ certMgr := &CertMgr{
@@ -97,6 +114,7 @@ func InitCertMgr(opts *Option, clientSet kubernetes.Interface, config *Config) (
configMgr: configMgr, configMgr: configMgr,
secretMgr: secretMgr, secretMgr: secretMgr,
cache: cache, cache: cache,
XDSUpdater: XDSUpdater,
} }
certMgr.cfg.OnEvent = certMgr.OnEvent certMgr.cfg.OnEvent = certMgr.OnEvent
return certMgr, nil return certMgr, nil
@@ -149,18 +167,31 @@ func (s *CertMgr) Reconcile(ctx context.Context, oldConfig *Config, newConfig *C
// sync email // sync email
s.myACME.Email = newIssuer.Email s.myACME.Email = newIssuer.Email
// sync RenewalWindowRatio // sync RenewalWindowRatio
s.cfg.RenewalWindowRatio = float64(newConfig.RenewBeforeDays / RenewMaxDays) renewalWindowRatio := float64(newConfig.RenewBeforeDays) / float64(RenewMaxDays)
s.cfg.RenewalWindowRatio = renewalWindowRatio
// start cache // start cache
s.cache.Start() s.cache.Start()
// sync domains // sync domains
s.manageSync(context.Background(), newDomains)
s.configMgr.SetConfig(newConfig) s.configMgr.SetConfig(newConfig)
CertLog.Infof("certMgr start to manageSync domains:+v%", newDomains)
s.manageSync(context.Background(), newDomains)
CertLog.Infof("certMgr manageSync domains done")
} else { } else {
// stop cache maintainAssets // stop cache maintainAssets
s.cache.Stop() s.cache.Stop()
s.configMgr.SetConfig(newConfig) s.configMgr.SetConfig(newConfig)
} }
if oldConfig != nil && newConfig != nil {
if oldConfig.FallbackForInvalidSecret != newConfig.FallbackForInvalidSecret || !reflect.DeepEqual(oldConfig.CredentialConfig, newConfig.CredentialConfig) {
CertLog.Infof("ingress need to full push")
s.XDSUpdater.ConfigUpdate(&model.PushRequest{
Full: true,
Reason: []model.TriggerReason{"higress-https-updated"},
})
}
}
return nil return nil
} }

View File

@@ -86,22 +86,35 @@ func (c *Config) GetSecretNameByDomain(issuerName IssuerName, domain string) str
return "" return ""
} }
func ParseTLSSecret(tlsSecret string) (string, string) {
secrets := strings.Split(tlsSecret, "/")
switch len(secrets) {
case 1:
return "", tlsSecret
case 2:
return secrets[0], secrets[1]
}
return "", ""
}
func (c *Config) Validate() error { func (c *Config) Validate() error {
// check acmeIssuer // check acmeIssuer
if len(c.ACMEIssuer) == 0 { if c.AutomaticHttps {
return fmt.Errorf("acmeIssuer is empty") if len(c.ACMEIssuer) == 0 {
} return fmt.Errorf("no acmeIssuer configuration found when automaticHttps is enable")
for _, issuer := range c.ACMEIssuer { }
switch issuer.Name { for _, issuer := range c.ACMEIssuer {
case IssuerTypeLetsencrypt: switch issuer.Name {
if issuer.Email == "" { case IssuerTypeLetsencrypt:
return fmt.Errorf("acmeIssuer %s email is empty", issuer.Name) 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)
} }
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 // check credentialConfig
@@ -111,14 +124,20 @@ func (c *Config) Validate() error {
} }
if credential.TLSSecret == "" { if credential.TLSSecret == "" {
return fmt.Errorf("credentialConfig tlsSecret is empty") return fmt.Errorf("credentialConfig tlsSecret is empty")
} else {
ns, secret := ParseTLSSecret(credential.TLSSecret)
if ns == "" && secret == "" {
return fmt.Errorf("credentialConfig tlsSecret %s is not supported", credential.TLSSecret)
}
} }
if credential.TLSIssuer == IssuerTypeLetsencrypt { if credential.TLSIssuer == IssuerTypeLetsencrypt {
if len(credential.Domains) > 1 { if len(credential.Domains) > 1 {
return fmt.Errorf("credentialConfig tlsIssuer %s only support one domain", credential.TLSIssuer) return fmt.Errorf("credentialConfig tlsIssuer %s only support one domain", credential.TLSIssuer)
} }
} }
if credential.TLSIssuer != IssuerTypeLetsencrypt && len(credential.TLSIssuer) > 0 { if credential.TLSIssuer != IssuerTypeLetsencrypt && len(credential.TLSIssuer) > 0 {
return fmt.Errorf("credential tls issuer %s is not support", credential.TLSIssuer) return fmt.Errorf("credential tls issuer %s is not supported", credential.TLSIssuer)
} }
} }

View File

@@ -120,3 +120,36 @@ func TestMatchSecretNameByDomain(t *testing.T) {
}) })
} }
} }
func TestParseTLSSecret(t *testing.T) {
tests := []struct {
tlsSecret string
expectedNamespace string
expectedSecretName string
}{
{
tlsSecret: "example-com-tls",
expectedNamespace: "",
expectedSecretName: "example-com-tls",
},
{
tlsSecret: "kube-system/example-com-tls",
expectedNamespace: "kube-system",
expectedSecretName: "example-com-tls",
},
{
tlsSecret: "kube-system/example-com/wildcard",
expectedNamespace: "",
expectedSecretName: "",
},
}
for _, tt := range tests {
t.Run(tt.tlsSecret, func(t *testing.T) {
resultNamespace, resultSecretName := ParseTLSSecret(tt.tlsSecret)
assert.Equal(t, tt.expectedNamespace, resultNamespace)
assert.Equal(t, tt.expectedSecretName, resultSecretName)
})
}
}

View File

@@ -18,7 +18,6 @@ import (
"context" "context"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@@ -27,10 +26,6 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
) )
const (
SecretNamePrefix = "higress-secret-"
)
type SecretMgr struct { type SecretMgr struct {
client kubernetes.Interface client kubernetes.Interface
namespace string namespace string
@@ -46,13 +41,21 @@ func NewSecretMgr(namespace string, client kubernetes.Interface) (*SecretMgr, er
} }
func (s *SecretMgr) Update(domain string, secretName string, privateKey []byte, certificate []byte, notBefore time.Time, notAfter time.Time, isRenew bool) error { 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) CertLog.Infof("update secret, domain:%s, secretName:%s, notBefore:%v, notAfter:%v, isRenew:%t", domain, secretName, notBefore, notAfter, isRenew)
secret := s.constructSecret(domain, privateKey, certificate, notBefore, notAfter, isRenew) name := secretName
_, err := s.client.CoreV1().Secrets(s.namespace).Get(context.Background(), secretName, metav1.GetOptions{}) namespace := s.namespace
namespaceP, secretP := ParseTLSSecret(secretName)
if namespaceP != "" {
namespace = namespaceP
name = secretP
}
secret := s.constructSecret(domain, name, namespace, privateKey, certificate, notBefore, notAfter, isRenew)
_, err := s.client.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{})
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
// create secret // create secret
_, err2 := s.client.CoreV1().Secrets(s.namespace).Create(context.Background(), secret, metav1.CreateOptions{}) _, err2 := s.client.CoreV1().Secrets(namespace).Create(context.Background(), secret, metav1.CreateOptions{})
return err2 return err2
} }
return err return err
@@ -61,7 +64,7 @@ func (s *SecretMgr) Update(domain string, secretName string, privateKey []byte,
if _, ok := secret.Annotations["higress.io/cert-domain"]; !ok { 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) 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{}) _, err1 := s.client.CoreV1().Secrets(namespace).Update(context.Background(), secret, metav1.UpdateOptions{})
if err1 != nil { if err1 != nil {
return err1 return err1
} }
@@ -69,23 +72,13 @@ func (s *SecretMgr) Update(domain string, secretName string, privateKey []byte,
return nil return nil
} }
func (s *SecretMgr) Delete(domain string) error { func (s *SecretMgr) constructSecret(domain string, name string, namespace string, privateKey []byte, certificate []byte, notBefore time.Time, notAfter time.Time, isRenew bool) *v1.Secret {
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 := make(map[string]string, 0)
annotationMap["higress.io/cert-domain"] = domain annotationMap["higress.io/cert-domain"] = domain
annotationMap["higress.io/cert-notAfter"] = notAfter.Format("2006-01-02 15:04:05") 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-notBefore"] = notBefore.Format("2006-01-02 15:04:05")
annotationMap["higress.io/cert-renew"] = strconv.FormatBool(isRenew) annotationMap["higress.io/cert-renew"] = strconv.FormatBool(isRenew)
annotationMap["higress.io/cert-source"] = string(IssuerTypeLetsencrypt)
if isRenew { if isRenew {
annotationMap["higress.io/cert-renew-time"] = time.Now().Format("2006-01-02 15:04:05") annotationMap["higress.io/cert-renew-time"] = time.Now().Format("2006-01-02 15:04:05")
} }
@@ -97,8 +90,8 @@ func (s *SecretMgr) constructSecret(domain string, privateKey []byte, certificat
dataMap["tls.crt"] = certificate dataMap["tls.crt"] = certificate
secret := &v1.Secret{ secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: secretName, Name: name,
Namespace: s.namespace, Namespace: namespace,
Annotations: annotationMap, Annotations: annotationMap,
}, },
Type: v1.SecretTypeTLS, Type: v1.SecretTypeTLS,

View File

@@ -22,6 +22,7 @@ import (
"time" "time"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"istio.io/istio/pilot/pkg/model"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
) )
@@ -37,12 +38,14 @@ type Server struct {
clientSet kubernetes.Interface clientSet kubernetes.Interface
controller *Controller controller *Controller
certMgr *CertMgr certMgr *CertMgr
XDSUpdater model.XDSUpdater
} }
func NewServer(clientSet kubernetes.Interface, opts *Option) (*Server, error) { func NewServer(clientSet kubernetes.Interface, XDSUpdater model.XDSUpdater, opts *Option) (*Server, error) {
server := &Server{ server := &Server{
clientSet: clientSet, clientSet: clientSet,
opts: opts, opts: opts,
XDSUpdater: XDSUpdater,
} }
return server, nil return server, nil
} }
@@ -65,7 +68,7 @@ func (s *Server) InitServer() error {
return err return err
} }
// init certmgr // init certmgr
certMgr, err := InitCertMgr(s.opts, s.clientSet, defaultConfig) // config and start certMgr, err := InitCertMgr(s.opts, s.clientSet, defaultConfig, s.XDSUpdater, configMgr) // config and start
s.certMgr = certMgr s.certMgr = certMgr
// init controller // init controller
controller, err := NewController(s.clientSet, s.opts.Namespace, certMgr, configMgr) controller, err := NewController(s.clientSet, s.opts.Namespace, certMgr, configMgr)

View File

@@ -32,7 +32,7 @@ import (
) )
const ( const (
CertificatesPrefix = "/certificates" CertificatesPrefix = "certificates"
ConfigmapStoreCertficatesPrefix = "higress-cert-store-certificates-" ConfigmapStoreCertficatesPrefix = "higress-cert-store-certificates-"
ConfigmapStoreDefaultName = "higress-cert-store-default" ConfigmapStoreDefaultName = "higress-cert-store-default"
) )
@@ -155,7 +155,7 @@ func (s *ConfigmapStorage) List(ctx context.Context, prefix string, recursive bo
// Check if the prefix corresponds to a specific key // Check if the prefix corresponds to a specific key
hashPrefix := fastHash([]byte(prefix)) hashPrefix := fastHash([]byte(prefix))
if strings.HasPrefix(prefix, CertificatesPrefix) { if strings.HasPrefix(prefix, CertificatesPrefix) {
// If the prefix is "/certificates", get all ConfigMaps and traverse each one // 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 // 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'"}) configmaps, err := s.client.CoreV1().ConfigMaps(s.namespace).List(ctx, metav1.ListOptions{FieldSelector: "metadata.annotations['higress.io/cert-https'] == 'true'"})
if err != nil { if err != nil {
@@ -289,14 +289,29 @@ func (s *ConfigmapStorage) String() string {
return "ConfigmapStorage" return "ConfigmapStorage"
} }
// getConfigmapStoreNameByKey determines the storage name for a given key.
// It checks if the key starts with 'certificates/' and if so, the key pattern should match one of the following:
// 'certificates/<issuerKey>/<domain>/<domain>.json',
// 'certificates/<issuerKey>/<domain>/<domain>.crt',
// or 'certificates/<issuerKey>/<domain>/<domain>.key'.
// It then returns the corresponding ConfigMap name.
// If the key does not start with 'certificates/', it returns the default store name.
//
// Parameters:
//
// key - The configuration map key that needs to be mapped to a storage name.
//
// Returns:
//
// string - The calculated or default storage name based on the key.
func (s *ConfigmapStorage) getConfigmapStoreNameByKey(key string) string { func (s *ConfigmapStorage) getConfigmapStoreNameByKey(key string) string {
parts := strings.SplitN(key, "/", 10) if strings.HasPrefix(key, "certificates/") {
if len(parts) >= 4 && parts[1] == "certificates" { parts := strings.Split(key, "/")
domain := strings.TrimSuffix(parts[3], ".crt") if len(parts) >= 4 && parts[0] == "certificates" {
domain = strings.TrimSuffix(domain, ".key") domain := parts[2]
domain = strings.TrimSuffix(domain, ".json") issuerKey := parts[1]
issuerKey := parts[2] return ConfigmapStoreCertficatesPrefix + fastHash([]byte(issuerKey+domain))
return ConfigmapStoreCertficatesPrefix + fastHash([]byte(issuerKey+domain)) }
} }
return ConfigmapStoreDefaultName return ConfigmapStoreDefaultName
} }

View File

@@ -39,22 +39,29 @@ func TestGetConfigmapStoreNameByKey(t *testing.T) {
}{ }{
{ {
name: "certificate crt", name: "certificate crt",
key: "/certificates/issuerKey/domain.crt", key: "certificates/issuerKey/domain/domain.crt",
expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")), expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")),
}, },
{
name: "47.237.14.136.sslip.io crt",
key: "certificates/acme-v02.api.letsencrypt.org-directory/47.237.14.136.sslip.io/47.237.14.136.sslip.io.crt",
expected: "higress-cert-store-certificates-" + fastHash([]byte("acme-v02.api.letsencrypt.org-directory"+"47.237.14.136.sslip.io")),
},
{ {
name: "certificate meta", name: "certificate meta",
key: "/certificates/issuerKey/domain.json", key: "certificates/issuerKey/domain/domain.json",
expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")), expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")),
}, },
{ {
name: "certificate key", name: "certificate key",
key: "/certificates/issuerKey/domain.key", key: "certificates/issuerKey/domain/domain.key",
expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")), expected: "higress-cert-store-certificates-" + fastHash([]byte("issuerKey"+"domain")),
}, },
{ {
name: "user key", name: "user key",
key: "/users/hello/2", key: "users/hello/2",
expected: "higress-cert-store-default", expected: "higress-cert-store-default",
}, },
{ {
@@ -82,7 +89,7 @@ func TestExists(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Store a test key // Store a test key
testKey := "/certificates/issuer1/domain1.crt" testKey := "certificates/issuer1/domain1/domain1.crt"
err = storage.Store(context.Background(), testKey, []byte("test-data")) err = storage.Store(context.Background(), testKey, []byte("test-data"))
assert.NoError(t, err) assert.NoError(t, err)
@@ -94,17 +101,17 @@ func TestExists(t *testing.T) {
}{ }{
{ {
name: "Existing Key", name: "Existing Key",
key: "/certificates/issuer1/domain1.crt", key: "certificates/issuer1/domain1/domain1.crt",
shouldExist: true, shouldExist: true,
}, },
{ {
name: "Non-Existent Key1", name: "Non-Existent Key1",
key: "/certificates/issuer2/domain2.crt", key: "certificates/issuer2/domain2/domain2.crt",
shouldExist: false, shouldExist: false,
}, },
{ {
name: "Non-Existent Key2", name: "Non-Existent Key2",
key: "/users/hello/a", key: "users/hello/a",
shouldExist: false, shouldExist: false,
}, },
// Add more test cases as needed // Add more test cases as needed
@@ -129,7 +136,7 @@ func TestLoad(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Store a test key // Store a test key
testKey := "/certificates/issuer1/domain1.crt" testKey := "certificates/issuer1/domain1/domain1.crt"
testValue := []byte("test-data") testValue := []byte("test-data")
err = storage.Store(context.Background(), testKey, testValue) err = storage.Store(context.Background(), testKey, testValue)
assert.NoError(t, err) assert.NoError(t, err)
@@ -143,13 +150,13 @@ func TestLoad(t *testing.T) {
}{ }{
{ {
name: "Existing Key", name: "Existing Key",
key: "/certificates/issuer1/domain1.crt", key: "certificates/issuer1/domain1/domain1.crt",
expected: testValue, expected: testValue,
shouldError: false, shouldError: false,
}, },
{ {
name: "Non-Existent Key", name: "Non-Existent Key",
key: "/certificates/issuer2/domain2.crt", key: "certificates/issuer2/domain2/domain2.crt",
expected: nil, expected: nil,
shouldError: true, shouldError: true,
}, },
@@ -192,28 +199,28 @@ func TestStore(t *testing.T) {
shouldError bool shouldError bool
}{ }{
{ {
name: "Store Key with /certificates prefix", name: "Store Key with certificates prefix",
key: "/certificates/issuer1/domain1.crt", key: "certificates/issuer1/domain1/domain1.crt",
value: []byte("test-data1"), value: []byte("test-data1"),
expected: map[string]string{fastHash([]byte("/certificates/issuer1/domain1.crt")): `{"k":"/certificates/issuer1/domain1.crt","v":"dGVzdC1kYXRhMQ=="}`}, expected: map[string]string{fastHash([]byte("certificates/issuer1/domain1/domain1.crt")): `{"k":"certificates/issuer1/domain1/domain1.crt","v":"dGVzdC1kYXRhMQ=="}`},
expectedConfigmapName: "higress-cert-store-certificates-" + fastHash([]byte("issuer1"+"domain1")), expectedConfigmapName: "higress-cert-store-certificates-" + fastHash([]byte("issuer1"+"domain1")),
shouldError: false, shouldError: false,
}, },
{ {
name: "Store Key with /certificates prefix (additional data)", name: "Store Key with certificates prefix (additional data)",
key: "/certificates/issuer2/domain2.crt", key: "certificates/issuer2/domain2/domain2.crt",
value: []byte("test-data2"), value: []byte("test-data2"),
expected: map[string]string{ expected: map[string]string{
fastHash([]byte("/certificates/issuer2/domain2.crt")): `{"k":"/certificates/issuer2/domain2.crt","v":"dGVzdC1kYXRhMg=="}`, fastHash([]byte("certificates/issuer2/domain2/domain2.crt")): `{"k":"certificates/issuer2/domain2/domain2.crt","v":"dGVzdC1kYXRhMg=="}`,
}, },
expectedConfigmapName: "higress-cert-store-certificates-" + fastHash([]byte("issuer2"+"domain2")), expectedConfigmapName: "higress-cert-store-certificates-" + fastHash([]byte("issuer2"+"domain2")),
shouldError: false, shouldError: false,
}, },
{ {
name: "Store Key without /certificates prefix", name: "Store Key without certificates prefix",
key: "/other/path/data.txt", key: "other/path/data.txt",
value: []byte("test-data3"), value: []byte("test-data3"),
expected: map[string]string{fastHash([]byte("/other/path/data.txt")): `{"k":"/other/path/data.txt","v":"dGVzdC1kYXRhMw=="}`}, expected: map[string]string{fastHash([]byte("other/path/data.txt")): `{"k":"other/path/data.txt","v":"dGVzdC1kYXRhMw=="}`},
expectedConfigmapName: "higress-cert-store-default", expectedConfigmapName: "higress-cert-store-default",
shouldError: false, shouldError: false,
}, },
@@ -256,17 +263,17 @@ func TestList(t *testing.T) {
// Store some test data // Store some test data
// Store some test data // Store some test data
testKeys := []string{ testKeys := []string{
"/certificates/issuer1/domain1.crt", "certificates/issuer1/domain1/domain1.crt",
"/certificates/issuer1/domain2.crt", "certificates/issuer1/domain2/domain2.crt",
"/certificates/issuer1/domain3.crt", // Added another domain for issuer1 "certificates/issuer1/domain3/domain3.crt", // Added another domain for issuer1
"/certificates/issuer2/domain4.crt", "certificates/issuer2/domain4/domain4.crt",
"/certificates/issuer2/domain5.crt", "certificates/issuer2/domain5/domain5.crt",
"/certificates/issuer3/subdomain1/domain6.crt", // Two-level subdirectory under issuer3 "certificates/issuer3/domain6/domain6.crt", // Two-level subdirectory under issuer3
"/certificates/issuer3/subdomain1/subdomain2/domain7.crt", // Two more levels under issuer3 "certificates/issuer3/subdomain1/subdomain2/domain7.crt", // Two more levels under issuer3
"/other-prefix/key1/file1", "other-prefix/key1/file1",
"/other-prefix/key1/file2", "other-prefix/key1/file2",
"/other-prefix/key2/file3", "other-prefix/key2/file3",
"/other-prefix/key2/file4", "other-prefix/key2/file4",
} }
for _, key := range testKeys { for _, key := range testKeys {
@@ -283,34 +290,34 @@ func TestList(t *testing.T) {
}{ }{
{ {
name: "List Certificates (Non-Recursive)", name: "List Certificates (Non-Recursive)",
prefix: "/certificates", prefix: "certificates",
recursive: false, recursive: false,
expected: []string{"/certificates/issuer1", "/certificates/issuer2", "/certificates/issuer3"}, expected: []string{"certificates/issuer1", "certificates/issuer2", "certificates/issuer3"},
}, },
{ {
name: "List Certificates (Recursive)", name: "List Certificates (Recursive)",
prefix: "/certificates", prefix: "certificates",
recursive: true, 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"}, expected: []string{"certificates/issuer1/domain1/domain1.crt", "certificates/issuer1/domain2/domain2.crt", "certificates/issuer1/domain3/domain3.crt", "certificates/issuer2/domain4/domain4.crt", "certificates/issuer2/domain5/domain5.crt", "certificates/issuer3/domain6/domain6.crt", "certificates/issuer3/subdomain1/subdomain2/domain7.crt"},
}, },
{ {
name: "List Other Prefix (Non-Recursive)", name: "List Other Prefix (Non-Recursive)",
prefix: "/other-prefix", prefix: "other-prefix",
recursive: false, recursive: false,
expected: []string{"/other-prefix/key1", "/other-prefix/key2"}, expected: []string{"other-prefix/key1", "other-prefix/key2"},
}, },
{ {
name: "List Other Prefix (Non-Recursive)", name: "List Other Prefix (Non-Recursive)",
prefix: "/other-prefix/key1", prefix: "other-prefix/key1",
recursive: false, recursive: false,
expected: []string{"/other-prefix/key1/file1", "/other-prefix/key1/file2"}, expected: []string{"other-prefix/key1/file1", "other-prefix/key1/file2"},
}, },
{ {
name: "List Other Prefix (Recursive)", name: "List Other Prefix (Recursive)",
prefix: "/other-prefix", prefix: "other-prefix",
recursive: true, recursive: true,
expected: []string{"/other-prefix/key1/file1", "/other-prefix/key1/file2", "/other-prefix/key2/file3", "/other-prefix/key2/file4"}, expected: []string{"other-prefix/key1/file1", "other-prefix/key1/file2", "other-prefix/key2/file3", "other-prefix/key2/file4"},
}, },
} }

View File

@@ -425,7 +425,7 @@ func openCommand(writer io.Writer, command string, args ...string) {
_, err := exec.LookPath(command) _, err := exec.LookPath(command)
if err != nil { if err != nil {
if errors.Is(err, exec.ErrNotFound) { if errors.Is(err, exec.ErrNotFound) {
fmt.Fprintf(writer, "Could not open your browser. Please open it maually.\n") fmt.Fprintf(writer, "Could not open your browser. Please open it manually.\n")
return return
} }
fmt.Fprintf(writer, "Failed to open browser; open %s in your browser.\nError: %s\n", args[0], err.Error()) fmt.Fprintf(writer, "Failed to open browser; open %s in your browser.\nError: %s\n", args[0], err.Error())

View File

@@ -28,7 +28,7 @@ import (
const ( const (
setFlagHelpStr = `Override an higress profile value, e.g. to choose a profile setFlagHelpStr = `Override an higress profile value, e.g. to choose a profile
(--set profile=local-k8s), or override profile values (--set gateway.replicas=2), or override helm values (--set values.global.proxy.resources.requsts.cpu=500m).` (--set profile=local-k8s), or override profile values (--set gateway.replicas=2), or override helm values (--set values.global.proxy.resources.requests.cpu=500m).`
// manifestsFlagHelpStr is the command line description for --manifests // manifestsFlagHelpStr is the command line description for --manifests
manifestsFlagHelpStr = `Specify a path to a directory of profiles manifestsFlagHelpStr = `Specify a path to a directory of profiles
(e.g. ~/Downloads/higress/manifests).` (e.g. ~/Downloads/higress/manifests).`
@@ -101,7 +101,7 @@ func newInstallCmd() *cobra.Command {
hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2" hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2"
# To override helm setting # To override helm setting
hgctl install --set profile=local-k8s --set values.global.proxy.resources.requsts.cpu=500m" hgctl install --set profile=local-k8s --set values.global.proxy.resources.requests.cpu=500m"
`, `,
@@ -175,7 +175,7 @@ func promptInstall(writer io.Writer, profileName string) bool {
func promptProfileName(writer io.Writer) string { func promptProfileName(writer io.Writer) string {
answer := "" answer := ""
fmt.Fprintf(writer, "\nPlease select higress install configration profile:\n") fmt.Fprintf(writer, "\nPlease select higress install configuration profile:\n")
fmt.Fprintf(writer, "\n1.Install higress to local kubernetes cluster like kind etc.\n") fmt.Fprintf(writer, "\n1.Install higress to local kubernetes cluster like kind etc.\n")
fmt.Fprintf(writer, "\n2.Install higress to kubernetes cluster\n") fmt.Fprintf(writer, "\n2.Install higress to kubernetes cluster\n")
fmt.Fprintf(writer, "\n3.Install higress to local docker environment\n") fmt.Fprintf(writer, "\n3.Install higress to local docker environment\n")

View File

@@ -176,7 +176,7 @@ func (a *Agent) checkSudoPermission() error {
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):
cmd2.Process.Signal(os.Interrupt) cmd2.Process.Signal(os.Interrupt)
if !a.quiet { if !a.quiet {
fmt.Fprintf(a.writer, "checked result: timeout execeed and need sudo with password\n") fmt.Fprintf(a.writer, "checked result: timeout exceed and need sudo with password\n")
} }
a.runSudoState = SudoWithPassword a.runSudoState = SudoWithPassword

View File

@@ -108,7 +108,7 @@ func upgrade(writer io.Writer, iArgs *InstallArgs) error {
func promptUpgrade(writer io.Writer) bool { func promptUpgrade(writer io.Writer) bool {
answer := "" answer := ""
for { for {
fmt.Fprintf(writer, "All Higress resources will be upgraed from the cluster. \nProceed? (y/N)") fmt.Fprintf(writer, "All Higress resources will be upgrade from the cluster. \nProceed? (y/N)")
fmt.Scanln(&answer) fmt.Scanln(&answer)
if strings.TrimSpace(answer) == "y" { if strings.TrimSpace(answer) == "y" {
fmt.Fprintf(writer, "\n") fmt.Fprintf(writer, "\n")
@@ -170,7 +170,7 @@ func promptProfileContexts(writer io.Writer, profileContexts []*installer.Profil
if len(profileContexts) == 1 { if len(profileContexts) == 1 {
fmt.Fprintf(writer, "\nFound a profile:: ") fmt.Fprintf(writer, "\nFound a profile:: ")
} else { } else {
fmt.Fprintf(writer, "\nPlease select higress installed configration profiles:\n") fmt.Fprintf(writer, "\nPlease select higress installed configuration profiles:\n")
} }
index := 1 index := 1
for _, profileContext := range profileContexts { for _, profileContext := range profileContexts {

View File

@@ -918,7 +918,7 @@ func (m *IngressConfig) AddOrUpdateWasmPlugin(clusterNamespacedName util.Cluster
Labels: map[string]string{constants.AlwaysPushLabel: "true"}, Labels: map[string]string{constants.AlwaysPushLabel: "true"},
} }
for _, f := range m.wasmPluginHandlers { for _, f := range m.wasmPluginHandlers {
IngressLog.Debug("WasmPlugin triggerd update") IngressLog.Debug("WasmPlugin triggered update")
f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, model.EventUpdate) f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, model.EventUpdate)
} }
istioWasmPlugin, err := m.convertIstioWasmPlugin(&wasmPlugin.Spec) istioWasmPlugin, err := m.convertIstioWasmPlugin(&wasmPlugin.Spec)
@@ -960,7 +960,7 @@ func (m *IngressConfig) DeleteWasmPlugin(clusterNamespacedName util.ClusterNames
Labels: map[string]string{constants.AlwaysPushLabel: "true"}, Labels: map[string]string{constants.AlwaysPushLabel: "true"},
} }
for _, f := range m.wasmPluginHandlers { for _, f := range m.wasmPluginHandlers {
IngressLog.Debug("WasmPlugin triggerd update") IngressLog.Debug("WasmPlugin triggered update")
f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, model.EventDelete) f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, model.EventDelete)
} }
} }
@@ -987,7 +987,7 @@ func (m *IngressConfig) AddOrUpdateMcpBridge(clusterNamespacedName util.ClusterN
Labels: map[string]string{constants.AlwaysPushLabel: "true"}, Labels: map[string]string{constants.AlwaysPushLabel: "true"},
} }
for _, f := range m.serviceEntryHandlers { for _, f := range m.serviceEntryHandlers {
IngressLog.Debug("McpBridge triggerd serviceEntry update") IngressLog.Debug("McpBridge triggered serviceEntry update")
f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, model.EventUpdate) f(config.Config{Meta: metadata}, config.Config{Meta: metadata}, model.EventUpdate)
} }
}, m.localKubeClient, m.namespace) }, m.localKubeClient, m.namespace)
@@ -1042,7 +1042,7 @@ func (m *IngressConfig) AddOrUpdateHttp2Rpc(clusterNamespacedName util.ClusterNa
} }
func (m *IngressConfig) DeleteHttp2Rpc(clusterNamespacedName util.ClusterNamespacedName) { func (m *IngressConfig) DeleteHttp2Rpc(clusterNamespacedName util.ClusterNamespacedName) {
IngressLog.Infof("Http2Rpc triggerd deleted event %s", clusterNamespacedName.Name) IngressLog.Infof("Http2Rpc triggered deleted event %s", clusterNamespacedName.Name)
if clusterNamespacedName.Namespace != m.namespace { if clusterNamespacedName.Namespace != m.namespace {
return return
} }
@@ -1054,7 +1054,7 @@ func (m *IngressConfig) DeleteHttp2Rpc(clusterNamespacedName util.ClusterNamespa
} }
m.mutex.Unlock() m.mutex.Unlock()
if hit { if hit {
IngressLog.Infof("Http2Rpc triggerd deleted event executed %s", clusterNamespacedName.Name) IngressLog.Infof("Http2Rpc triggered deleted event executed %s", clusterNamespacedName.Name)
push := func(kind config.GroupVersionKind) { push := func(kind config.GroupVersionKind) {
m.XDSUpdater.ConfigUpdate(&model.PushRequest{ m.XDSUpdater.ConfigUpdate(&model.PushRequest{
Full: true, Full: true,
@@ -1160,13 +1160,13 @@ func (m *IngressConfig) constructHttp2RpcEnvoyFilter(http2rpcConfig *annotations
IngressLog.Infof("Found http2rpc mappings %v", mappings) IngressLog.Infof("Found http2rpc mappings %v", mappings)
if _, exist := mappings[http2rpcConfig.Name]; !exist { if _, exist := mappings[http2rpcConfig.Name]; !exist {
IngressLog.Errorf("Http2RpcConfig name %s, not found Http2Rpc CRD", http2rpcConfig.Name) IngressLog.Errorf("Http2RpcConfig name %s, not found Http2Rpc CRD", http2rpcConfig.Name)
return nil, errors.New("invalid http2rpcConfig has no useable http2rpc") return nil, errors.New("invalid http2rpcConfig has no usable http2rpc")
} }
http2rpcCRD := mappings[http2rpcConfig.Name] http2rpcCRD := mappings[http2rpcConfig.Name]
if http2rpcCRD.GetDubbo() == nil { if http2rpcCRD.GetDubbo() == nil {
IngressLog.Errorf("Http2RpcConfig name %s, only support Http2Rpc CRD Dubbo Service type", http2rpcConfig.Name) IngressLog.Errorf("Http2RpcConfig name %s, only support Http2Rpc CRD Dubbo Service type", http2rpcConfig.Name)
return nil, errors.New("invalid http2rpcConfig has no useable http2rpc") return nil, errors.New("invalid http2rpcConfig has no usable http2rpc")
} }
httpRoute := route.HTTPRoute httpRoute := route.HTTPRoute
@@ -1293,7 +1293,7 @@ func (m *IngressConfig) constructHttp2RpcMethods(dubbo *higressv1.DubboService)
var method = make(map[string]interface{}) var method = make(map[string]interface{})
method["name"] = serviceMethod.GetServiceMethod() method["name"] = serviceMethod.GetServiceMethod()
var params []interface{} var params []interface{}
// paramFromEntireBody is for methods with single parameter. So when paramFromEntireBody exists, we just ignore parmas. // paramFromEntireBody is for methods with single parameter. So when paramFromEntireBody exists, we just ignore params.
var paramFromEntireBody = serviceMethod.GetParamFromEntireBody() var paramFromEntireBody = serviceMethod.GetParamFromEntireBody()
if paramFromEntireBody != nil { if paramFromEntireBody != nil {
var param = make(map[string]interface{}) var param = make(map[string]interface{})

View File

@@ -433,6 +433,11 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
// If there is no matching secret, try to get it from configmap. // If there is no matching secret, try to get it from configmap.
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host) secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace secretNamespace = c.options.SystemNamespace
namespace, secret := cert.ParseTLSSecret(secretName)
if namespace != "" {
secretNamespace = namespace
secretName = secret
}
} }
} }
} }
@@ -441,6 +446,11 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
if httpsCredentialConfig != nil { if httpsCredentialConfig != nil {
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host) secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace secretNamespace = c.options.SystemNamespace
namespace, secret := cert.ParseTLSSecret(secretName)
if namespace != "" {
secretNamespace = namespace
secretName = secret
}
} }
} }
if secretName == "" { if secretName == "" {

View File

@@ -419,6 +419,11 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
// If there is no matching secret, try to get it from configmap. // If there is no matching secret, try to get it from configmap.
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host) secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace secretNamespace = c.options.SystemNamespace
namespace, secret := cert.ParseTLSSecret(secretName)
if namespace != "" {
secretNamespace = namespace
secretName = secret
}
} }
} }
} }
@@ -427,6 +432,11 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
if httpsCredentialConfig != nil { if httpsCredentialConfig != nil {
secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host) secretName = httpsCredentialConfig.MatchSecretNameByDomain(rule.Host)
secretNamespace = c.options.SystemNamespace secretNamespace = c.options.SystemNamespace
namespace, secret := cert.ParseTLSSecret(secretName)
if namespace != "" {
secretNamespace = namespace
secretName = secret
}
} }
} }

View File

@@ -0,0 +1 @@
EXTRA_TAGS=proxy_wasm_version_0_2_100

View File

@@ -8,7 +8,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240528060522-53bccf89f441 github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240528060522-53bccf89f441
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
github.com/tidwall/resp v0.1.1 github.com/tidwall/resp v0.1.1
github.com/tidwall/sjson v1.2.5 github.com/tidwall/sjson v1.2.5

View File

@@ -5,6 +5,7 @@ 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/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 h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -222,9 +222,9 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte
log.Debugf("cache hit, key:%s", key) log.Debugf("cache hit, key:%s", key)
ctx.SetContext(CacheKeyContextKey, nil) ctx.SetContext(CacheKeyContextKey, nil)
if !stream { if !stream {
proxywasm.SendHttpResponse(200, [][2]string{{"content-type", "application/json; charset=utf-8"}}, []byte(fmt.Sprintf(config.ReturnResponseTemplate, response.String())), -1) proxywasm.SendHttpResponseWithDetail(200, "ai-cache.hit", [][2]string{{"content-type", "application/json; charset=utf-8"}}, []byte(fmt.Sprintf(config.ReturnResponseTemplate, response.String())), -1)
} else { } else {
proxywasm.SendHttpResponse(200, [][2]string{{"content-type", "text/event-stream; charset=utf-8"}}, []byte(fmt.Sprintf(config.ReturnStreamResponseTemplate, response.String())), -1) proxywasm.SendHttpResponseWithDetail(200, "ai-cache.hit", [][2]string{{"content-type", "text/event-stream; charset=utf-8"}}, []byte(fmt.Sprintf(config.ReturnStreamResponseTemplate, response.String())), -1)
} }
}) })
if err != nil { if err != nil {

View File

@@ -1,18 +1,12 @@
# 简介 # 简介
AI提示词修饰插件,通过在与大模型发起的请求前后插入指定信息来调整大模型的输出 AI提示词装饰器插件支持在LLM的请求前后插入prompt
# 配置说明 # 配置说明
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|----------------|-----------------|------|-----|----------------------------------|
| `decorators` | array of object | 必填 | - | 修饰设置 |
template object 配置说明:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|----------------|-----------------|------|-----|----------------------------------| |----------------|-----------------|------|-----|----------------------------------|
| `name` | string | 必填 | - | 修饰名称 | | `prepend` | array of message object | optional | - | 在初始输入之前插入的语句 |
| `decorator.prepend` | array of message object | 必填 | - | 在初始输入之插入的语句 | | `append` | array of message object | optional | - | 在初始输入之插入的语句 |
| `decorator.append` | array of message object | 必填 | - | 在初始输入之后插入的语句 |
message object 配置说明: message object 配置说明:
@@ -26,57 +20,50 @@ message object 配置说明:
配置示例如下: 配置示例如下:
```yaml ```yaml
decorators: prepend:
- name: "hangzhou-guide" - role: system
decorator: content: "请使用英语回答问题"
prepend: append:
- role: system - role: user
content: "You will always respond in the Chinese language." content: "每次回答完问题,尝试进行反问"
- role: user
content: "Assume you are from Hangzhou."
append:
- role: user
content: "Don't introduce Hangzhou's food."
``` ```
使用以上配置发起请求: 使用以上配置发起请求:
```bash ```bash
{ curl http://localhost/test \
-H "content-type: application/json" \
-d '{
"model": "gpt-3.5-turbo", "model": "gpt-3.5-turbo",
"messages": [ "messages": [
{ {
"role": "user", "role": "user",
"content": "Please introduce your home." "content": "你是谁?"
} }
] ]
} }
``` ```
响应如下 经过插件处理后,实际请求为
``` ```bash
{ curl http://localhost/test \
"id": "chatcmpl-9UYwQlEg6GwAswEZBDYXl41RU4gab", -H "content-type: application/json" \
"object": "chat.completion", -d '{
"created": 1717071182, "model": "gpt-3.5-turbo",
"model": "gpt-3.5-turbo-0125", "messages": [
"choices": [
{ {
"index": 0, "role": "system",
"message": { "content": "请使用英语回答问题"
"role": "assistant", },
"content": "杭州是一个美丽的城市,有着悠久的历史和富有特色的文化。这里风景优美,有西湖、雷峰塔等著名景点,吸引着许多游客前来观光。杭州人民热情好客,城市宁静安逸,是一个适合居住和旅游的地方。" {
}, "role": "user",
"logprobs": null, "content": "你是谁?"
"finish_reason": "stop" },
{
"role": "user",
"content": "每次回答完问题,尝试进行反问"
} }
], ]
"usage": {
"prompt_tokens": 49,
"completion_tokens": 117,
"total_tokens": 166
},
"system_fingerprint": null
} }
``` ```

View File

@@ -2,9 +2,11 @@ module ai-prompt-decorator
go 1.18 go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.3.5 github.com/alibaba/higress/plugins/wasm-go v1.3.5
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

View File

@@ -1,12 +1,10 @@
github.com/alibaba/higress/plugins/wasm-go v1.3.5 h1:VOLL3m442IHCSu8mR5AZ4sc6LVT9X0w1hdqDI7oB9jY=
github.com/alibaba/higress/plugins/wasm-go v1.3.5/go.mod h1:kr3V9Ntbspj1eSrX8rgjBsdMXkGupYEf+LM72caGPQc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 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/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 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew= 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-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg=
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-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -1,8 +1,7 @@
package main package main
import ( import (
"errors" "encoding/json"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" "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"
@@ -20,66 +19,53 @@ func main() {
) )
} }
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type AIPromptDecoratorConfig struct { type AIPromptDecoratorConfig struct {
decorators map[string]string Prepend []Message `json:"prepend"`
Append []Message `json:"append"`
} }
func removeBrackets(raw string) (string, error) { func parseConfig(jsonConfig gjson.Result, config *AIPromptDecoratorConfig, log wrapper.Log) error {
startIndex := strings.Index(raw, "{") return json.Unmarshal([]byte(jsonConfig.Raw), config)
endIndex := strings.LastIndex(raw, "}")
if startIndex == -1 || endIndex == -1 {
return raw, errors.New("message format is wrong!")
} else {
return raw[startIndex : endIndex+1], nil
}
}
func parseConfig(json gjson.Result, config *AIPromptDecoratorConfig, log wrapper.Log) error {
config.decorators = make(map[string]string)
for _, v := range json.Get("decorators").Array() {
config.decorators[v.Get("name").String()] = v.Get("decorator").Raw
// log.Info(v.Get("decorator").Raw)
}
return nil
} }
func onHttpRequestHeaders(ctx wrapper.HttpContext, config AIPromptDecoratorConfig, log wrapper.Log) types.Action { func onHttpRequestHeaders(ctx wrapper.HttpContext, config AIPromptDecoratorConfig, log wrapper.Log) types.Action {
decorator, _ := proxywasm.GetHttpRequestHeader("decorator")
if decorator == "" {
ctx.DontReadRequestBody()
return types.ActionContinue
}
ctx.SetContext("decorator", decorator)
proxywasm.RemoveHttpRequestHeader("decorator")
proxywasm.RemoveHttpRequestHeader("content-length") proxywasm.RemoveHttpRequestHeader("content-length")
return types.ActionContinue return types.ActionContinue
} }
func onHttpRequestBody(ctx wrapper.HttpContext, config AIPromptDecoratorConfig, body []byte, log wrapper.Log) types.Action { func onHttpRequestBody(ctx wrapper.HttpContext, config AIPromptDecoratorConfig, body []byte, log wrapper.Log) types.Action {
decoratorName := ctx.GetContext("decorator").(string)
decorator := config.decorators[decoratorName]
messageJson := `{"messages":[]}` messageJson := `{"messages":[]}`
prependMessage := gjson.Get(decorator, "prepend") for _, entry := range config.Prepend {
if prependMessage.Exists() { msg, err := json.Marshal(entry)
for _, entry := range prependMessage.Array() { if err != nil {
messageJson, _ = sjson.SetRaw(messageJson, "messages.-1", entry.Raw) log.Errorf("Failed to add prepend message, error: %v", err)
return types.ActionContinue
} }
messageJson, _ = sjson.SetRaw(messageJson, "messages.-1", string(msg))
} }
rawMessage := gjson.GetBytes(body, "messages") rawMessage := gjson.GetBytes(body, "messages")
if rawMessage.Exists() { if !rawMessage.Exists() {
for _, entry := range rawMessage.Array() { log.Errorf("Cannot find messages field in request body")
messageJson, _ = sjson.SetRaw(messageJson, "messages.-1", entry.Raw) return types.ActionContinue
} }
for _, entry := range rawMessage.Array() {
messageJson, _ = sjson.SetRaw(messageJson, "messages.-1", entry.Raw)
} }
appendMessage := gjson.Get(decorator, "append") for _, entry := range config.Append {
if appendMessage.Exists() { msg, err := json.Marshal(entry)
for _, entry := range appendMessage.Array() { if err != nil {
messageJson, _ = sjson.SetRaw(messageJson, "messages.-1", entry.Raw) log.Errorf("Failed to add prepend message, error: %v", err)
return types.ActionContinue
} }
messageJson, _ = sjson.SetRaw(messageJson, "messages.-1", string(msg))
} }
newbody, err := sjson.SetRaw(string(body), "messages", gjson.Get(messageJson, "messages").Raw) newbody, err := sjson.SetRaw(string(body), "messages", gjson.Get(messageJson, "messages").Raw)

View File

@@ -2,9 +2,11 @@ module ai-prompt-template
go 1.18 go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.3.5 github.com/alibaba/higress/plugins/wasm-go v1.3.5
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

View File

@@ -7,6 +7,7 @@ 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/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 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-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -0,0 +1 @@
EXTRA_TAGS=proxy_wasm_version_0_2_100

View File

@@ -19,14 +19,14 @@ description: AI 代理插件配置参考
`provider`的配置字段说明如下: `provider`的配置字段说明如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------------- | --------------- | -------- | ------ | ------------------------------------------------------------ | | -------------- | --------------- | -------- | ------ |-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `type` | string | 必填 | - | AI 服务提供商名称 | | `type` | string | 必填 | - | AI 服务提供商名称 |
| `apiTokens` | array of string | 必填 | - | 用于在访问 AI 服务时进行认证的令牌。如果配置了多个 token插件会在请求时随机进行选择。部分服务提供商只支持配置一个 token。 | | `apiTokens` | array of string | 必填 | - | 用于在访问 AI 服务时进行认证的令牌。如果配置了多个 token插件会在请求时随机进行选择。部分服务提供商只支持配置一个 token。 |
| `timeout` | number | 非必填 | - | 访问 AI 服务的超时时间。单位为毫秒。默认值为 120000即 2 分钟 | | `timeout` | number | 非必填 | - | 访问 AI 服务的超时时间。单位为毫秒。默认值为 120000即 2 分钟 |
| `modelMapping` | map of string | 非必填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。<br/>可以使用 "*" 为键来配置通用兜底映射关系 | | `modelMapping` | map of string | 非必填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。<br/>1. 支持前缀匹配。例如用 "gpt-3-*" 匹配所有名称以“gpt-3-”开头的模型;<br/>2. 支持使用 "*" 为键来配置通用兜底映射关系;<br/>3. 如果映射的目标名称为空字符串 "",则表示保留原模型名称。 |
| `protocol` | string | 非必填 | - | 插件对外提供的 API 接口契约。目前支持以下取值openai默认值使用 OpenAI 的接口契约、original使用目标服务提供商的原始接口契约 | | `protocol` | string | 非必填 | - | 插件对外提供的 API 接口契约。目前支持以下取值openai默认值使用 OpenAI 的接口契约、original使用目标服务提供商的原始接口契约 |
| `context` | object | 非必填 | - | 配置 AI 对话上下文信息 | | `context` | object | 非必填 | - | 配置 AI 对话上下文信息 |
`context`的配置字段说明如下: `context`的配置字段说明如下:
@@ -131,6 +131,15 @@ Ollama 所对应的 `type` 为 `ollama`。它特有的配置字段如下:
阶跃星辰所对应的 `type``stepfun`。它并无特有的配置字段。 阶跃星辰所对应的 `type``stepfun`。它并无特有的配置字段。
#### Cloudflare Workers AI
Cloudflare Workers AI 所对应的 `type``cloudflare`。它特有的配置字段如下:
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|-------------------|--------|------|-----|----------------------------------------------------------------------------------------------------------------------------|
| `cloudflareAccountId` | string | 必填 | - | [Cloudflare Account ID](https://developers.cloudflare.com/workers-ai/get-started/rest-api/#1-get-api-token-and-account-id) |
## 用法示例 ## 用法示例
### 使用 OpenAI 协议代理 Azure OpenAI 服务 ### 使用 OpenAI 协议代理 Azure OpenAI 服务
@@ -246,25 +255,72 @@ provider:
'gpt-3': "qwen-turbo" 'gpt-3': "qwen-turbo"
'gpt-35-turbo': "qwen-plus" 'gpt-35-turbo': "qwen-plus"
'gpt-4-turbo': "qwen-max" 'gpt-4-turbo': "qwen-max"
'gpt-4-*': "qwen-max"
'text-embedding-v1': 'text-embedding-v1'
'*': "qwen-turbo" '*': "qwen-turbo"
``` ```
**AI 对话请求示例**
URL: http://your-domain/v1/chat/completions
请求体:
```json
{
"model": "text-embedding-v1",
"input": "Hello"
}
```
响应体示例:
```json
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [
-1.0437825918197632,
5.208984375,
3.0483806133270264,
-1.7897135019302368,
-2.0107421875,
...,
0.8125,
-1.1759847402572632,
0.8174641728401184,
1.0432943105697632,
-0.5885213017463684
]
}
],
"model": "text-embedding-v1",
"usage": {
"prompt_tokens": 1,
"total_tokens": 1
}
}
```
**请求示例** **请求示例**
URL: http://your-domain/v1/embeddings
示例请求内容:
```json ```json
{ {
"model": "gpt-3", "model": "text-embedding-v1",
"messages": [ "input": [
{ "Hello world!"
"role": "user", ]
"content": "你好,你是谁?"
}
],
"temperature": 0.3
} }
``` ```
**响应示例** 示例响应内容:
```json ```json
{ {
@@ -758,6 +814,57 @@ provider:
} }
``` ```
### 使用 OpenAI 协议代理 Cloudflare Workers AI 服务
**配置信息**
```yaml
provider:
type: cloudflare
apiTokens:
- "YOUR_WORKERS_AI_API_TOKEN"
cloudflareAccountId: "YOUR_CLOUDFLARE_ACCOUNT_ID"
modelMapping:
"*": "@cf/meta/llama-3-8b-instruct"
```
**请求示例**
```json
{
"model": "gpt-3.5",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Who are you?"
}
]
}
```
**响应示例**
```json
{
"id": "id-1720367803430",
"object": "chat.completion",
"created": 1720367803,
"model": "@cf/meta/llama-3-8b-instruct",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "I 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 conversation and answer questions to the best of my knowledge. I can be used to generate text on a wide range of topics, from science and history to entertainment and culture.\n\nI'm a large language model, which means I've been trained on a massive dataset of text from the internet and can generate human-like responses. I can understand natural language and respond accordingly, making me suitable for tasks such as:\n\n* Answering questions on various topics\n* Generating text based on a given prompt\n* Translating text from one language to another\n* Summarizing long pieces of text\n* Creating chatbot dialogues\n\nI'm constantly learning and improving, so the more conversations I have with users like you, the better I'll become."
},
"logprobs": null,
"finish_reason": "stop"
}
]
}
```
## 完整配置示例 ## 完整配置示例
### Kubernetes 示例 ### Kubernetes 示例

View File

@@ -8,7 +8,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0 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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

View File

@@ -4,8 +4,8 @@ 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/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 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew= 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-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo= github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -21,6 +21,8 @@ const (
pluginName = "ai-proxy" pluginName = "ai-proxy"
ctxKeyApiName = "apiKey" ctxKeyApiName = "apiKey"
defaultMaxBodyBytes uint32 = 10 * 1024 * 1024
) )
func main() { func main() {
@@ -61,10 +63,10 @@ func onHttpRequestHeader(ctx wrapper.HttpContext, pluginConfig config.PluginConf
rawPath := ctx.Path() rawPath := ctx.Path()
path, _ := url.Parse(rawPath) path, _ := url.Parse(rawPath)
apiName := getApiName(path.Path) apiName := getOpenAiApiName(path.Path)
if apiName == "" { if apiName == "" {
log.Debugf("[onHttpRequestHeader] unsupported path: %s", path.Path) log.Debugf("[onHttpRequestHeader] unsupported path: %s", path.Path)
_ = util.SendResponse(404, util.MimeTypeTextPlain, "API not found: "+path.Path) _ = util.SendResponse(404, "ai-proxy.unknown_api", util.MimeTypeTextPlain, "API not found: "+path.Path)
return types.ActionContinue return types.ActionContinue
} }
ctx.SetContext(ctxKeyApiName, apiName) ctx.SetContext(ctxKeyApiName, apiName)
@@ -75,16 +77,18 @@ func onHttpRequestHeader(ctx wrapper.HttpContext, pluginConfig config.PluginConf
action, err := handler.OnRequestHeaders(ctx, apiName, log) action, err := handler.OnRequestHeaders(ctx, apiName, log)
if err == nil { if err == nil {
if contentType, err := proxywasm.GetHttpRequestHeader("Content-Type"); err == nil && contentType != "" {
ctx.SetRequestBodyBufferLimit(defaultMaxBodyBytes)
// Always return types.HeaderStopIteration to support fallback routing,
// as long as onHttpRequestBody can be called.
return types.HeaderStopIteration
}
return action return action
} }
_ = util.SendResponse(404, util.MimeTypeTextPlain, fmt.Sprintf("failed to process request headers: %v", err)) _ = util.SendResponse(500, "ai-proxy.proc_req_headers_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to process request headers: %v", err))
return types.ActionContinue return types.ActionContinue
} }
if _, needHandleBody := activeProvider.(provider.RequestBodyHandler); needHandleBody {
ctx.DontReadRequestBody()
}
return types.ActionContinue return types.ActionContinue
} }
@@ -99,18 +103,24 @@ func onHttpRequestBody(ctx wrapper.HttpContext, pluginConfig config.PluginConfig
log.Debugf("[onHttpRequestBody] provider=%s", activeProvider.GetProviderType()) log.Debugf("[onHttpRequestBody] provider=%s", activeProvider.GetProviderType())
if handler, ok := activeProvider.(provider.RequestBodyHandler); ok { if handler, ok := activeProvider.(provider.RequestBodyHandler); ok {
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName) apiName, _ := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
action, err := handler.OnRequestBody(ctx, apiName, body, log) action, err := handler.OnRequestBody(ctx, apiName, body, log)
if err == nil { if err == nil {
return action return action
} }
_ = util.SendResponse(404, util.MimeTypeTextPlain, fmt.Sprintf("failed to process request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.proc_req_body_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to process request body: %v", err))
return types.ActionContinue return types.ActionContinue
} }
return types.ActionContinue return types.ActionContinue
} }
func onHttpResponseHeaders(ctx wrapper.HttpContext, pluginConfig config.PluginConfig, log wrapper.Log) types.Action { func onHttpResponseHeaders(ctx wrapper.HttpContext, pluginConfig config.PluginConfig, log wrapper.Log) types.Action {
if !wrapper.IsResponseFromUpstream() {
// Response is not coming from the upstream. Let it pass through.
ctx.DontReadResponseBody()
return types.ActionContinue
}
activeProvider := pluginConfig.GetProvider() activeProvider := pluginConfig.GetProvider()
if activeProvider == nil { if activeProvider == nil {
@@ -139,12 +149,12 @@ func onHttpResponseHeaders(ctx wrapper.HttpContext, pluginConfig config.PluginCo
} }
if handler, ok := activeProvider.(provider.ResponseHeadersHandler); ok { if handler, ok := activeProvider.(provider.ResponseHeadersHandler); ok {
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName) apiName, _ := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
action, err := handler.OnResponseHeaders(ctx, apiName, log) action, err := handler.OnResponseHeaders(ctx, apiName, log)
if err == nil { if err == nil {
return action return action
} }
_ = util.SendResponse(404, util.MimeTypeTextPlain, fmt.Sprintf("failed to process response headers: %v", err)) _ = util.SendResponse(500, "ai-proxy.proc_resp_headers_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to process response headers: %v", err))
return types.ActionContinue return types.ActionContinue
} }
@@ -171,7 +181,7 @@ func onStreamingResponseBody(ctx wrapper.HttpContext, pluginConfig config.Plugin
log.Debugf("isLastChunk=%v chunk: %s", isLastChunk, string(chunk)) log.Debugf("isLastChunk=%v chunk: %s", isLastChunk, string(chunk))
if handler, ok := activeProvider.(provider.StreamingResponseBodyHandler); ok { if handler, ok := activeProvider.(provider.StreamingResponseBodyHandler); ok {
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName) apiName, _ := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
modifiedChunk, err := handler.OnStreamingResponseBody(ctx, apiName, chunk, isLastChunk, log) modifiedChunk, err := handler.OnStreamingResponseBody(ctx, apiName, chunk, isLastChunk, log)
if err == nil && modifiedChunk != nil { if err == nil && modifiedChunk != nil {
return modifiedChunk return modifiedChunk
@@ -193,20 +203,23 @@ func onHttpResponseBody(ctx wrapper.HttpContext, pluginConfig config.PluginConfi
//log.Debugf("response body: %s", string(body)) //log.Debugf("response body: %s", string(body))
if handler, ok := activeProvider.(provider.ResponseBodyHandler); ok { if handler, ok := activeProvider.(provider.ResponseBodyHandler); ok {
apiName := ctx.GetContext(ctxKeyApiName).(provider.ApiName) apiName, _ := ctx.GetContext(ctxKeyApiName).(provider.ApiName)
action, err := handler.OnResponseBody(ctx, apiName, body, log) action, err := handler.OnResponseBody(ctx, apiName, body, log)
if err == nil { if err == nil {
return action return action
} }
_ = util.SendResponse(404, util.MimeTypeTextPlain, fmt.Sprintf("failed to process response body: %v", err)) _ = util.SendResponse(500, "ai-proxy.proc_resp_body_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to process response body: %v", err))
return types.ActionContinue return types.ActionContinue
} }
return types.ActionContinue return types.ActionContinue
} }
func getApiName(path string) provider.ApiName { func getOpenAiApiName(path string) provider.ApiName {
if strings.HasSuffix(path, "/v1/chat/completions") { if strings.HasSuffix(path, "/v1/chat/completions") {
return provider.ApiNameChatCompletion return provider.ApiNameChatCompletion
} }
if strings.HasSuffix(path, "/v1/embeddings") {
return provider.ApiNameEmbeddings
}
return "" return ""
} }

View File

@@ -58,13 +58,7 @@ func (m *azureProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiNam
_ = util.OverwriteRequestPath(m.serviceUrl.RequestURI()) _ = util.OverwriteRequestPath(m.serviceUrl.RequestURI())
_ = util.OverwriteRequestHost(m.serviceUrl.Host) _ = util.OverwriteRequestHost(m.serviceUrl.Host)
_ = proxywasm.ReplaceHttpRequestHeader("api-key", m.config.apiTokens[0]) _ = proxywasm.ReplaceHttpRequestHeader("api-key", m.config.apiTokens[0])
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
if m.contextCache == nil {
ctx.DontReadRequestBody()
} else {
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
}
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -85,11 +79,11 @@ func (m *azureProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.azure.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.azure.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -45,14 +45,8 @@ func (m *baichuanProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName Api
} }
_ = util.OverwriteRequestPath(baichuanChatCompletionPath) _ = util.OverwriteRequestPath(baichuanChatCompletionPath)
_ = util.OverwriteRequestHost(baichuanDomain) _ = util.OverwriteRequestHost(baichuanDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
if m.contextCache == nil {
ctx.DontReadRequestBody()
} else {
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
}
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -73,11 +67,11 @@ func (m *baichuanProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiNam
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.baichuan.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.baichuan.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -94,11 +94,11 @@ func (b *baiduProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.baidu.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
b.setSystemContent(request, content) b.setSystemContent(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.baidu.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -137,12 +137,12 @@ func (b *baiduProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.baidu.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
baiduRequest := b.baiduTextGenRequest(request) baiduRequest := b.baiduTextGenRequest(request)
if err := replaceJsonRequestBody(baiduRequest, log); err != nil { if err := replaceJsonRequestBody(baiduRequest, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace Request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.baidu.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace Request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -298,11 +298,11 @@ func (b *baiduProvider) responseBaidu2OpenAI(ctx wrapper.HttpContext, response *
return &chatCompletionResponse{ return &chatCompletionResponse{
Id: response.Id, Id: response.Id,
Created: time.Now().UnixMilli() / 1000, Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string), Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
SystemFingerprint: "", SystemFingerprint: "",
Object: objectChatCompletion, Object: objectChatCompletion,
Choices: []chatCompletionChoice{choice}, Choices: []chatCompletionChoice{choice},
Usage: chatCompletionUsage{ Usage: usage{
PromptTokens: response.Usage.PromptTokens, PromptTokens: response.Usage.PromptTokens,
CompletionTokens: response.Usage.CompletionTokens, CompletionTokens: response.Usage.CompletionTokens,
TotalTokens: response.Usage.TotalTokens, TotalTokens: response.Usage.TotalTokens,
@@ -321,11 +321,11 @@ func (b *baiduProvider) streamResponseBaidu2OpenAI(ctx wrapper.HttpContext, resp
return &chatCompletionResponse{ return &chatCompletionResponse{
Id: response.Id, Id: response.Id,
Created: time.Now().UnixMilli() / 1000, Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string), Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
SystemFingerprint: "", SystemFingerprint: "",
Object: objectChatCompletion, Object: objectChatCompletion,
Choices: []chatCompletionChoice{choice}, Choices: []chatCompletionChoice{choice},
Usage: chatCompletionUsage{ Usage: usage{
PromptTokens: response.Usage.PromptTokens, PromptTokens: response.Usage.PromptTokens,
CompletionTokens: response.Usage.CompletionTokens, CompletionTokens: response.Usage.CompletionTokens,
TotalTokens: response.Usage.TotalTokens, TotalTokens: response.Usage.TotalTokens,

View File

@@ -139,10 +139,10 @@ func (c *claudeProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.claude.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.claude.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -185,12 +185,12 @@ func (c *claudeProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.claude.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
claudeRequest := c.buildClaudeTextGenRequest(request) claudeRequest := c.buildClaudeTextGenRequest(request)
if err := replaceJsonRequestBody(claudeRequest, log); err != nil { if err := replaceJsonRequestBody(claudeRequest, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.claude.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -292,11 +292,11 @@ func (c *claudeProvider) responseClaude2OpenAI(ctx wrapper.HttpContext, origResp
return &chatCompletionResponse{ return &chatCompletionResponse{
Id: origResponse.Id, Id: origResponse.Id,
Created: time.Now().UnixMilli() / 1000, Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string), Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
SystemFingerprint: "", SystemFingerprint: "",
Object: objectChatCompletion, Object: objectChatCompletion,
Choices: []chatCompletionChoice{choice}, Choices: []chatCompletionChoice{choice},
Usage: chatCompletionUsage{ Usage: usage{
PromptTokens: origResponse.Usage.InputTokens, PromptTokens: origResponse.Usage.InputTokens,
CompletionTokens: origResponse.Usage.OutputTokens, CompletionTokens: origResponse.Usage.OutputTokens,
TotalTokens: origResponse.Usage.InputTokens + origResponse.Usage.OutputTokens, TotalTokens: origResponse.Usage.InputTokens + origResponse.Usage.OutputTokens,
@@ -356,7 +356,7 @@ func createChatCompletionResponse(ctx wrapper.HttpContext, response *claudeTextG
return &chatCompletionResponse{ return &chatCompletionResponse{
Id: response.Message.Id, Id: response.Message.Id,
Created: time.Now().UnixMilli() / 1000, Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string), Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Object: objectChatCompletionChunk, Object: objectChatCompletionChunk,
Choices: []chatCompletionChoice{choice}, Choices: []chatCompletionChoice{choice},
} }

View File

@@ -0,0 +1,106 @@
package provider
import (
"errors"
"fmt"
"strings"
"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 (
cloudflareDomain = "api.cloudflare.com"
// https://developers.cloudflare.com/workers-ai/configuration/open-ai-compatibility/
cloudflareChatCompletionPath = "/client/v4/accounts/{account_id}/ai/v1/chat/completions"
)
type cloudflareProviderInitializer struct {
}
func (c *cloudflareProviderInitializer) ValidateConfig(config ProviderConfig) error {
return nil
}
func (c *cloudflareProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) {
return &cloudflareProvider{
config: config,
contextCache: createContextCache(&config),
}, nil
}
type cloudflareProvider struct {
config ProviderConfig
contextCache *contextCache
}
func (c *cloudflareProvider) GetProviderType() string {
return providerTypeCloudflare
}
func (c *cloudflareProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
if apiName != ApiNameChatCompletion {
return types.ActionContinue, errUnsupportedApiName
}
_ = util.OverwriteRequestPath(strings.Replace(cloudflareChatCompletionPath, "{account_id}", c.config.cloudflareAccountId, 1))
_ = util.OverwriteRequestHost(cloudflareDomain)
_ = util.OverwriteRequestAuthorization("Bearer " + c.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Accept-Encoding")
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
return types.ActionContinue, nil
}
func (c *cloudflareProvider) 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")
}
ctx.SetContext(ctxKeyOriginalRequestModel, model)
mappedModel := getMappedModel(model, c.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")
}
if c.contextCache == nil {
if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, "ai-proxy.cloudflare.transform_body_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
}
return types.ActionContinue, nil
}
err := c.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, "ai-proxy.cloudflare.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
}
insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, "ai-proxy.cloudflare.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
}
}, log)
if err == nil {
return types.ActionPause, nil
}
return types.ActionContinue, err
}

View File

@@ -45,14 +45,8 @@ func (m *deepseekProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName Api
} }
_ = util.OverwriteRequestPath(deepseekChatCompletionPath) _ = util.OverwriteRequestPath(deepseekChatCompletionPath)
_ = util.OverwriteRequestHost(deepseekDomain) _ = util.OverwriteRequestHost(deepseekDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
if m.contextCache == nil {
ctx.DontReadRequestBody()
} else {
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
}
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -73,11 +67,11 @@ func (m *deepseekProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiNam
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.deepseek.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.deepseek.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -43,14 +43,8 @@ func (m *groqProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName
} }
_ = util.OverwriteRequestPath(groqChatCompletionPath) _ = util.OverwriteRequestPath(groqChatCompletionPath)
_ = util.OverwriteRequestHost(groqDomain) _ = util.OverwriteRequestHost(groqDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
if m.contextCache == nil {
ctx.DontReadRequestBody()
} else {
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
}
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -71,11 +65,11 @@ func (m *groqProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, b
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.groq.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.groq.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -155,7 +155,7 @@ func (m *hunyuanProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName
// 根据确定好的payload进行签名 // 根据确定好的payload进行签名
hunyuanBody, _ := json.Marshal(request) hunyuanBody, _ := json.Marshal(request)
authorizedValueNew := GetTC3Authorizationcode(m.config.hunyuanAuthId, m.config.hunyuanAuthKey, timestamp, hunyuanDomain, hunyuanChatCompletionTCAction, string(hunyuanBody)) authorizedValueNew := GetTC3Authorizationcode(m.config.hunyuanAuthId, m.config.hunyuanAuthKey, timestamp, hunyuanDomain, hunyuanChatCompletionTCAction, string(hunyuanBody))
_ = proxywasm.ReplaceHttpRequestHeader(authorizationKey, authorizedValueNew) _ = util.OverwriteRequestAuthorization(authorizedValueNew)
_ = proxywasm.ReplaceHttpRequestHeader("Accept", "*/*") _ = proxywasm.ReplaceHttpRequestHeader("Accept", "*/*")
// log.Debugf("#debug nash5# OnRequestBody call hunyuan api using original api! signature computation done!") // log.Debugf("#debug nash5# OnRequestBody call hunyuan api using original api! signature computation done!")
@@ -171,17 +171,17 @@ func (m *hunyuanProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.hunyuan.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
m.insertContextMessageIntoHunyuanRequest(request, content) m.insertContextMessageIntoHunyuanRequest(request, content)
// 因为手动插入了context内容这里需要重新计算签名 // 因为手动插入了context内容这里需要重新计算签名
hunyuanBody, _ := json.Marshal(request) hunyuanBody, _ := json.Marshal(request)
authorizedValueNew := GetTC3Authorizationcode(m.config.hunyuanAuthId, m.config.hunyuanAuthKey, timestamp, hunyuanDomain, hunyuanChatCompletionTCAction, string(hunyuanBody)) authorizedValueNew := GetTC3Authorizationcode(m.config.hunyuanAuthId, m.config.hunyuanAuthKey, timestamp, hunyuanDomain, hunyuanChatCompletionTCAction, string(hunyuanBody))
_ = proxywasm.ReplaceHttpRequestHeader(authorizationKey, authorizedValueNew) _ = util.OverwriteRequestAuthorization(authorizedValueNew)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.hunyuan.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -234,7 +234,7 @@ func (m *hunyuanProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName
hunyuanChatCompletionTCAction, hunyuanChatCompletionTCAction,
string(body), string(body),
) )
_ = proxywasm.ReplaceHttpRequestHeader(authorizationKey, authorizedValueNew) _ = util.OverwriteRequestAuthorization(authorizedValueNew)
// log.Debugf("#debug nash5# OnRequestBody done, body is: ", string(body)) // log.Debugf("#debug nash5# OnRequestBody done, body is: ", string(body))
// // 打印所有的headers // // 打印所有的headers
@@ -256,7 +256,7 @@ func (m *hunyuanProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.hunyuan.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
return return
} }
insertContextMessage(request, content) insertContextMessage(request, content)
@@ -265,10 +265,10 @@ func (m *hunyuanProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName
// 因为手动插入了context内容这里需要重新计算签名 // 因为手动插入了context内容这里需要重新计算签名
hunyuanBody, _ := json.Marshal(hunyuanRequest) hunyuanBody, _ := json.Marshal(hunyuanRequest)
authorizedValueNew := GetTC3Authorizationcode(m.config.hunyuanAuthId, m.config.hunyuanAuthKey, timestamp, hunyuanDomain, hunyuanChatCompletionTCAction, string(hunyuanBody)) authorizedValueNew := GetTC3Authorizationcode(m.config.hunyuanAuthId, m.config.hunyuanAuthKey, timestamp, hunyuanDomain, hunyuanChatCompletionTCAction, string(hunyuanBody))
_ = proxywasm.ReplaceHttpRequestHeader(authorizationKey, authorizedValueNew) _ = util.OverwriteRequestAuthorization(authorizedValueNew)
if err := replaceJsonRequestBody(hunyuanRequest, log); err != nil { if err := replaceJsonRequestBody(hunyuanRequest, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.hunyuan.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -351,10 +351,10 @@ func (m *hunyuanProvider) convertChunkFromHunyuanToOpenAI(ctx wrapper.HttpContex
openAIFormattedChunk := &chatCompletionResponse{ openAIFormattedChunk := &chatCompletionResponse{
Id: hunyuanFormattedChunk.Id, Id: hunyuanFormattedChunk.Id,
Created: time.Now().UnixMilli() / 1000, Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string), Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
SystemFingerprint: "", SystemFingerprint: "",
Object: objectChatCompletionChunk, Object: objectChatCompletionChunk,
Usage: chatCompletionUsage{ Usage: usage{
PromptTokens: hunyuanFormattedChunk.Usage.PromptTokens, PromptTokens: hunyuanFormattedChunk.Usage.PromptTokens,
CompletionTokens: hunyuanFormattedChunk.Usage.CompletionTokens, CompletionTokens: hunyuanFormattedChunk.Usage.CompletionTokens,
TotalTokens: hunyuanFormattedChunk.Usage.TotalTokens, TotalTokens: hunyuanFormattedChunk.Usage.TotalTokens,
@@ -470,11 +470,11 @@ func (m *hunyuanProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, h
return &chatCompletionResponse{ return &chatCompletionResponse{
Id: hunyuanResponse.Response.Id, Id: hunyuanResponse.Response.Id,
Created: time.Now().UnixMilli() / 1000, Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string), Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
SystemFingerprint: "", SystemFingerprint: "",
Object: objectChatCompletion, Object: objectChatCompletion,
Choices: choices, Choices: choices,
Usage: chatCompletionUsage{ Usage: usage{
PromptTokens: hunyuanResponse.Response.Usage.PromptTokens, PromptTokens: hunyuanResponse.Response.Usage.PromptTokens,
CompletionTokens: hunyuanResponse.Response.Usage.CompletionTokens, CompletionTokens: hunyuanResponse.Response.Usage.CompletionTokens,
TotalTokens: hunyuanResponse.Response.Usage.TotalTokens, TotalTokens: hunyuanResponse.Response.Usage.TotalTokens,

View File

@@ -4,11 +4,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util" "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" "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"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"strings"
) )
// minimaxProvider is the provider for minimax service. // minimaxProvider is the provider for minimax service.
@@ -75,7 +76,7 @@ func (m *minimaxProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiN
return types.ActionContinue, errUnsupportedApiName return types.ActionContinue, errUnsupportedApiName
} }
_ = util.OverwriteRequestHost(minimaxDomain) _ = util.OverwriteRequestHost(minimaxDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Content-Length") _ = proxywasm.RemoveHttpRequestHeader("Content-Length")
// Delay the header processing to allow changing streaming mode in OnRequestBody // Delay the header processing to allow changing streaming mode in OnRequestBody
@@ -135,11 +136,11 @@ func (m *minimaxProvider) handleRequestBodyByChatCompletionPro(body []byte, log
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.minimax.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
m.setBotSettings(request, content) m.setBotSettings(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.minimax.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -168,11 +169,11 @@ func (m *minimaxProvider) handleRequestBodyByChatCompletionPro(body []byte, log
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.minimax.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
minimaxRequest := m.buildMinimaxChatCompletionV2Request(request, content) minimaxRequest := m.buildMinimaxChatCompletionV2Request(request, content)
if err := replaceJsonRequestBody(minimaxRequest, log); err != nil { if err := replaceJsonRequestBody(minimaxRequest, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace Request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.minimax.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace Request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -202,11 +203,11 @@ func (m *minimaxProvider) handleRequestBodyByChatCompletionV2(body []byte, log w
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.minimax.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.minimax.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -222,9 +223,9 @@ func (m *minimaxProvider) OnResponseHeaders(ctx wrapper.HttpContext, apiName Api
return types.ActionContinue, nil return types.ActionContinue, nil
} }
// 模型对应接口为ChatCompletion v2,跳过OnStreamingResponseBody()和OnResponseBody() // 模型对应接口为ChatCompletion v2,跳过OnStreamingResponseBody()和OnResponseBody()
model := ctx.GetContext(ctxKeyFinalRequestModel) model := ctx.GetStringContext(ctxKeyFinalRequestModel, "")
if model != nil { if model != "" {
_, ok := chatCompletionProModels[model.(string)] _, ok := chatCompletionProModels[model]
if !ok { if !ok {
ctx.DontReadResponseBody() ctx.DontReadResponseBody()
return types.ActionContinue, nil return types.ActionContinue, nil
@@ -461,7 +462,7 @@ func (m *minimaxProvider) responseV2ToOpenAI(response *minimaxChatCompletionV2Re
Created: response.Created, Created: response.Created,
Model: response.Model, Model: response.Model,
Choices: choices, Choices: choices,
Usage: chatCompletionUsage{ Usage: usage{
TotalTokens: int(response.Usage.TotalTokens), TotalTokens: int(response.Usage.TotalTokens),
}, },
} }

View File

@@ -60,7 +60,7 @@ type chatCompletionResponse struct {
Model string `json:"model,omitempty"` Model string `json:"model,omitempty"`
SystemFingerprint string `json:"system_fingerprint,omitempty"` SystemFingerprint string `json:"system_fingerprint,omitempty"`
Object string `json:"object,omitempty"` Object string `json:"object,omitempty"`
Usage chatCompletionUsage `json:"usage,omitempty"` Usage usage `json:"usage,omitempty"`
} }
type chatCompletionChoice struct { type chatCompletionChoice struct {
@@ -70,7 +70,7 @@ type chatCompletionChoice struct {
FinishReason string `json:"finish_reason,omitempty"` FinishReason string `json:"finish_reason,omitempty"`
} }
type chatCompletionUsage struct { type usage struct {
PromptTokens int `json:"prompt_tokens,omitempty"` PromptTokens int `json:"prompt_tokens,omitempty"`
CompletionTokens int `json:"completion_tokens,omitempty"` CompletionTokens int `json:"completion_tokens,omitempty"`
TotalTokens int `json:"total_tokens,omitempty"` TotalTokens int `json:"total_tokens,omitempty"`
@@ -140,3 +140,24 @@ func (e *streamEvent) setValue(key, value string) {
} }
} }
} }
type embeddingsRequest struct {
Input interface{} `json:"input"`
Model string `json:"model"`
EncodingFormat string `json:"encoding_format,omitempty"`
Dimensions int `json:"dimensions,omitempty"`
User string `json:"user,omitempty"`
}
type embeddingsResponse struct {
Object string `json:"object"`
Data []embedding `json:"data"`
Model string `json:"model"`
Usage usage `json:"usage"`
}
type embedding struct {
Object string `json:"object"`
Index int `json:"index"`
Embedding []float64 `json:"embedding"`
}

View File

@@ -57,7 +57,7 @@ func (m *moonshotProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName Api
} }
_ = util.OverwriteRequestPath(moonshotChatCompletionPath) _ = util.OverwriteRequestPath(moonshotChatCompletionPath)
_ = util.OverwriteRequestHost(moonshotDomain) _ = util.OverwriteRequestHost(moonshotDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Content-Length") _ = proxywasm.RemoveHttpRequestHeader("Content-Length")
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -92,12 +92,12 @@ func (m *moonshotProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiNam
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.moonshot.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
return return
} }
err = m.performChatCompletion(ctx, content, request, log) err = m.performChatCompletion(ctx, content, request, log)
if err != nil { if err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to perform chat completion: %v", err)) _ = util.SendResponse(500, "ai-proxy.moonshot.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to perform chat completion: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -68,7 +68,7 @@ func (m *ollamaProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
if m.config.modelMapping == nil && m.contextCache == nil { if m.config.modelMapping == nil && m.contextCache == nil {
return types.ActionContinue, nil return types.ActionContinue, nil
} }
request := &chatCompletionRequest{} request := &chatCompletionRequest{}
if err := decodeChatCompletionRequest(body, request); err != nil { if err := decodeChatCompletionRequest(body, request); err != nil {
return types.ActionContinue, err return types.ActionContinue, err
@@ -83,7 +83,7 @@ func (m *ollamaProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
return types.ActionContinue, errors.New("model becomes empty after applying the configured mapping") return types.ActionContinue, errors.New("model becomes empty after applying the configured mapping")
} }
request.Model = mappedModel request.Model = mappedModel
if m.contextCache != nil { if m.contextCache != nil {
err := m.contextCache.GetContent(func(content string, err error) { err := m.contextCache.GetContent(func(content string, err error) {
defer func() { defer func() {
@@ -91,11 +91,11 @@ func (m *ollamaProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.ollama.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.ollama.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -105,7 +105,7 @@ func (m *ollamaProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName,
} }
} else { } else {
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.ollama.transform_body_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
return types.ActionContinue, err return types.ActionContinue, err
} }
_ = proxywasm.ResumeHttpRequest() _ = proxywasm.ResumeHttpRequest()

View File

@@ -14,6 +14,7 @@ import (
const ( const (
openaiDomain = "api.openai.com" openaiDomain = "api.openai.com"
openaiChatCompletionPath = "/v1/chat/completions" openaiChatCompletionPath = "/v1/chat/completions"
openaiEmbeddingsPath = "/v1/chat/embeddings"
) )
type openaiProviderInitializer struct { type openaiProviderInitializer struct {
@@ -40,44 +41,60 @@ func (m *openaiProvider) GetProviderType() string {
} }
func (m *openaiProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) { func (m *openaiProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
if apiName != ApiNameChatCompletion { switch apiName {
return types.ActionContinue, errUnsupportedApiName case ApiNameChatCompletion:
_ = util.OverwriteRequestPath(openaiChatCompletionPath)
break
case ApiNameEmbeddings:
_ = util.OverwriteRequestPath(openaiEmbeddingsPath)
break
} }
_ = util.OverwriteRequestPath(openaiChatCompletionPath) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = util.OverwriteRequestHost(openaiDomain) _ = proxywasm.RemoveHttpRequestHeader("Content-Length")
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken())
if m.contextCache == nil {
ctx.DontReadRequestBody()
} else {
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
}
return types.ActionContinue, nil return types.ActionContinue, nil
} }
func (m *openaiProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) { func (m *openaiProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
if apiName != ApiNameChatCompletion { if apiName != ApiNameChatCompletion {
return types.ActionContinue, errUnsupportedApiName // We don't need to process the request body for other APIs.
}
if m.contextCache == nil {
return types.ActionContinue, nil return types.ActionContinue, nil
} }
request := &chatCompletionRequest{} request := &chatCompletionRequest{}
if err := decodeChatCompletionRequest(body, request); err != nil { if err := decodeChatCompletionRequest(body, request); err != nil {
return types.ActionContinue, err return types.ActionContinue, err
} }
bodyAltered := false
if request.Stream {
// For stream requests, we need to include usage in the response.
if request.StreamOptions == nil {
request.StreamOptions = &streamOptions{IncludeUsage: true}
bodyAltered = true
} else if !request.StreamOptions.IncludeUsage {
request.StreamOptions.IncludeUsage = true
bodyAltered = true
}
}
if m.contextCache == nil {
if bodyAltered {
if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, "ai-proxy.openai.set_include_usage_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
}
}
return types.ActionContinue, nil
} else {
// If context cache is configured and body has been altered, the new body will be replaced when inserting the context data.
}
err := m.contextCache.GetContent(func(content string, err error) { err := m.contextCache.GetContent(func(content string, err error) {
defer func() { defer func() {
_ = proxywasm.ResumeHttpRequest() _ = proxywasm.ResumeHttpRequest()
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.openai.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.openai.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -3,6 +3,7 @@ package provider
import ( import (
"errors" "errors"
"math/rand" "math/rand"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
@@ -14,22 +15,24 @@ type Pointcut string
const ( const (
ApiNameChatCompletion ApiName = "chatCompletion" ApiNameChatCompletion ApiName = "chatCompletion"
ApiNameEmbeddings ApiName = "embeddings"
providerTypeMoonshot = "moonshot" providerTypeMoonshot = "moonshot"
providerTypeAzure = "azure" providerTypeAzure = "azure"
providerTypeQwen = "qwen" providerTypeQwen = "qwen"
providerTypeOpenAI = "openai" providerTypeOpenAI = "openai"
providerTypeGroq = "groq" providerTypeGroq = "groq"
providerTypeBaichuan = "baichuan" providerTypeBaichuan = "baichuan"
providerTypeYi = "yi" providerTypeYi = "yi"
providerTypeDeepSeek = "deepseek" providerTypeDeepSeek = "deepseek"
providerTypeZhipuAi = "zhipuai" providerTypeZhipuAi = "zhipuai"
providerTypeOllama = "ollama" providerTypeOllama = "ollama"
providerTypeClaude = "claude" providerTypeClaude = "claude"
providerTypeBaidu = "baidu" providerTypeBaidu = "baidu"
providerTypeHunyuan = "hunyuan" providerTypeHunyuan = "hunyuan"
providerTypeStepfun = "stepfun" providerTypeStepfun = "stepfun"
providerTypeMinimax = "minimax" providerTypeMinimax = "minimax"
providerTypeCloudflare = "cloudflare"
protocolOpenAI = "openai" protocolOpenAI = "openai"
protocolOriginal = "original" protocolOriginal = "original"
@@ -65,21 +68,22 @@ var (
errUnsupportedApiName = errors.New("unsupported API name") errUnsupportedApiName = errors.New("unsupported API name")
providerInitializers = map[string]providerInitializer{ providerInitializers = map[string]providerInitializer{
providerTypeMoonshot: &moonshotProviderInitializer{}, providerTypeMoonshot: &moonshotProviderInitializer{},
providerTypeAzure: &azureProviderInitializer{}, providerTypeAzure: &azureProviderInitializer{},
providerTypeQwen: &qwenProviderInitializer{}, providerTypeQwen: &qwenProviderInitializer{},
providerTypeOpenAI: &openaiProviderInitializer{}, providerTypeOpenAI: &openaiProviderInitializer{},
providerTypeGroq: &groqProviderInitializer{}, providerTypeGroq: &groqProviderInitializer{},
providerTypeBaichuan: &baichuanProviderInitializer{}, providerTypeBaichuan: &baichuanProviderInitializer{},
providerTypeYi: &yiProviderInitializer{}, providerTypeYi: &yiProviderInitializer{},
providerTypeDeepSeek: &deepseekProviderInitializer{}, providerTypeDeepSeek: &deepseekProviderInitializer{},
providerTypeZhipuAi: &zhipuAiProviderInitializer{}, providerTypeZhipuAi: &zhipuAiProviderInitializer{},
providerTypeOllama: &ollamaProviderInitializer{}, providerTypeOllama: &ollamaProviderInitializer{},
providerTypeClaude: &claudeProviderInitializer{}, providerTypeClaude: &claudeProviderInitializer{},
providerTypeBaidu: &baiduProviderInitializer{}, providerTypeBaidu: &baiduProviderInitializer{},
providerTypeHunyuan: &hunyuanProviderInitializer{}, providerTypeHunyuan: &hunyuanProviderInitializer{},
providerTypeStepfun: &stepfunProviderInitializer{}, providerTypeStepfun: &stepfunProviderInitializer{},
providerTypeMinimax: &minimaxProviderInitializer{}, providerTypeMinimax: &minimaxProviderInitializer{},
providerTypeCloudflare: &cloudflareProviderInitializer{},
} }
) )
@@ -156,6 +160,9 @@ type ProviderConfig struct {
// @Title zh-CN 版本 // @Title zh-CN 版本
// @Description zh-CN 请求AI服务的版本目前仅适用于Claude AI服务 // @Description zh-CN 请求AI服务的版本目前仅适用于Claude AI服务
claudeVersion string `required:"false" yaml:"version" json:"version"` claudeVersion string `required:"false" yaml:"version" json:"version"`
// @Title zh-CN Cloudflare Account ID
// @Description zh-CN 仅适用于 Cloudflare Workers AI 服务。参考https://developers.cloudflare.com/workers-ai/get-started/rest-api/#2-run-a-model-via-api
cloudflareAccountId string `required:"false" yaml:"cloudflareAccountId" json:"cloudflareAccountId"`
} }
func (c *ProviderConfig) FromJson(json gjson.Result) { func (c *ProviderConfig) FromJson(json gjson.Result) {
@@ -194,6 +201,7 @@ func (c *ProviderConfig) FromJson(json gjson.Result) {
c.hunyuanAuthId = json.Get("hunyuanAuthId").String() c.hunyuanAuthId = json.Get("hunyuanAuthId").String()
c.hunyuanAuthKey = json.Get("hunyuanAuthKey").String() c.hunyuanAuthKey = json.Get("hunyuanAuthKey").String()
c.minimaxGroupId = json.Get("minimaxGroupId").String() c.minimaxGroupId = json.Get("minimaxGroupId").String()
c.cloudflareAccountId = json.Get("cloudflareAccountId").String()
} }
func (c *ProviderConfig) Validate() error { func (c *ProviderConfig) Validate() error {
@@ -247,16 +255,38 @@ func CreateProvider(pc ProviderConfig) (Provider, error) {
} }
func getMappedModel(model string, modelMapping map[string]string, log wrapper.Log) string { func getMappedModel(model string, modelMapping map[string]string, log wrapper.Log) string {
if modelMapping == nil || len(modelMapping) == 0 { mappedModel := doGetMappedModel(model, modelMapping, log)
return model if len(mappedModel) != 0 {
} return mappedModel
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 return model
} }
func doGetMappedModel(model string, modelMapping map[string]string, log wrapper.Log) string {
if modelMapping == nil || len(modelMapping) == 0 {
return ""
}
if v, ok := modelMapping[model]; ok {
log.Debugf("model [%s] is mapped to [%s] explictly", model, v)
return v
}
for k, v := range modelMapping {
if k == wildcard || !strings.HasSuffix(k, wildcard) {
continue
}
k = strings.TrimSuffix(k, wildcard)
if strings.HasPrefix(model, k) {
log.Debugf("model [%s] is mapped to [%s] via prefix [%s]", model, v, k)
return v
}
}
if v, ok := modelMapping[wildcard]; ok {
log.Debugf("model [%s] is mapped to [%s] via wildcard", model, v)
return v
}
return ""
}

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"reflect"
"strings" "strings"
"time" "time"
@@ -21,6 +22,7 @@ const (
qwenDomain = "dashscope.aliyuncs.com" qwenDomain = "dashscope.aliyuncs.com"
qwenChatCompletionPath = "/api/v1/services/aigc/text-generation/generation" qwenChatCompletionPath = "/api/v1/services/aigc/text-generation/generation"
qwenTextEmbeddingPath = "/api/v1/services/embeddings/text-embedding/text-embedding"
qwenTopPMin = 0.000001 qwenTopPMin = 0.000001
qwenTopPMax = 0.999999 qwenTopPMax = 0.999999
@@ -58,15 +60,17 @@ func (m *qwenProvider) GetProviderType() string {
} }
func (m *qwenProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) { func (m *qwenProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
if apiName != ApiNameChatCompletion { if apiName == ApiNameChatCompletion {
_ = util.OverwriteRequestPath(qwenChatCompletionPath)
} else if apiName == ApiNameEmbeddings {
_ = util.OverwriteRequestPath(qwenTextEmbeddingPath)
} else {
return types.ActionContinue, errUnsupportedApiName return types.ActionContinue, errUnsupportedApiName
} }
_ = util.OverwriteRequestPath(qwenChatCompletionPath)
_ = util.OverwriteRequestHost(qwenDomain) _ = util.OverwriteRequestHost(qwenDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
if m.config.protocol == protocolOriginal && m.config.context == nil { if m.config.protocol == protocolOriginal {
ctx.DontReadRequestBody()
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -78,10 +82,16 @@ func (m *qwenProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName
} }
func (m *qwenProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) { func (m *qwenProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
if apiName != ApiNameChatCompletion { if apiName == ApiNameChatCompletion {
return types.ActionContinue, errUnsupportedApiName return m.onChatCompletionRequestBody(ctx, body, log)
} }
if apiName == ApiNameEmbeddings {
return m.onEmbeddingsRequestBody(ctx, body, log)
}
return types.ActionContinue, errUnsupportedApiName
}
func (m *qwenProvider) onChatCompletionRequestBody(ctx wrapper.HttpContext, body []byte, log wrapper.Log) (types.Action, error) {
if m.config.protocol == protocolOriginal { if m.config.protocol == protocolOriginal {
if m.config.context == nil { if m.config.context == nil {
return types.ActionContinue, nil return types.ActionContinue, nil
@@ -99,11 +109,11 @@ func (m *qwenProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, b
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.qwen.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
m.insertContextMessage(request, content, false) m.insertContextMessage(request, content, false)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.qwen.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -152,7 +162,7 @@ func (m *qwenProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, b
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.qwen.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
qwenRequest := m.buildQwenTextGenerationRequest(request, streaming) qwenRequest := m.buildQwenTextGenerationRequest(request, streaming)
@@ -160,7 +170,7 @@ func (m *qwenProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, b
ctx.SetContext(ctxKeyIncrementalStreaming, qwenRequest.Parameters.IncrementalOutput) ctx.SetContext(ctxKeyIncrementalStreaming, qwenRequest.Parameters.IncrementalOutput)
} }
if err := replaceJsonRequestBody(qwenRequest, log); err != nil { if err := replaceJsonRequestBody(qwenRequest, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.qwen.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {
@@ -169,6 +179,33 @@ func (m *qwenProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, b
return types.ActionContinue, err return types.ActionContinue, err
} }
func (m *qwenProvider) onEmbeddingsRequestBody(ctx wrapper.HttpContext, body []byte, log wrapper.Log) (types.Action, error) {
request := &embeddingsRequest{}
if err := json.Unmarshal(body, request); err != nil {
return types.ActionContinue, fmt.Errorf("unable to unmarshal request: %v", err)
}
log.Debugf("=== embeddings request: %v", request)
model := request.Model
if model == "" {
return types.ActionContinue, errors.New("missing model in the 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)
if qwenRequest, err := m.buildQwenTextEmbeddingRequest(request); err == nil {
return types.ActionContinue, replaceJsonRequestBody(qwenRequest, log)
} else {
return types.ActionContinue, err
}
}
func (m *qwenProvider) OnResponseHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) { func (m *qwenProvider) OnResponseHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) {
if m.config.protocol == protocolOriginal { if m.config.protocol == protocolOriginal {
ctx.DontReadResponseBody() ctx.DontReadResponseBody()
@@ -180,15 +217,16 @@ func (m *qwenProvider) OnResponseHeaders(ctx wrapper.HttpContext, apiName ApiNam
} }
func (m *qwenProvider) OnStreamingResponseBody(ctx wrapper.HttpContext, name ApiName, chunk []byte, isLastChunk bool, log wrapper.Log) ([]byte, error) { func (m *qwenProvider) OnStreamingResponseBody(ctx wrapper.HttpContext, name ApiName, chunk []byte, isLastChunk bool, log wrapper.Log) ([]byte, error) {
if name != ApiNameChatCompletion {
return chunk, nil
}
receivedBody := chunk receivedBody := chunk
if bufferedStreamingBody, has := ctx.GetContext(ctxKeyStreamingBody).([]byte); has { if bufferedStreamingBody, has := ctx.GetContext(ctxKeyStreamingBody).([]byte); has {
receivedBody = append(bufferedStreamingBody, chunk...) receivedBody = append(bufferedStreamingBody, chunk...)
} }
incrementalStreaming, err := ctx.GetContext(ctxKeyIncrementalStreaming).(bool) incrementalStreaming := ctx.GetBoolContext(ctxKeyIncrementalStreaming, false)
if !err {
incrementalStreaming = false
}
eventStartIndex, lineStartIndex, valueStartIndex := -1, -1, -1 eventStartIndex, lineStartIndex, valueStartIndex := -1, -1, -1
@@ -264,6 +302,16 @@ func (m *qwenProvider) OnStreamingResponseBody(ctx wrapper.HttpContext, name Api
} }
func (m *qwenProvider) OnResponseBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) { func (m *qwenProvider) OnResponseBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) (types.Action, error) {
if apiName == ApiNameChatCompletion {
return m.onChatCompletionResponseBody(ctx, body, log)
}
if apiName == ApiNameEmbeddings {
return m.onEmbeddingsResponseBody(ctx, body, log)
}
return types.ActionContinue, errUnsupportedApiName
}
func (m *qwenProvider) onChatCompletionResponseBody(ctx wrapper.HttpContext, body []byte, log wrapper.Log) (types.Action, error) {
qwenResponse := &qwenTextGenResponse{} qwenResponse := &qwenTextGenResponse{}
if err := json.Unmarshal(body, qwenResponse); err != nil { if err := json.Unmarshal(body, qwenResponse); err != nil {
return types.ActionContinue, fmt.Errorf("unable to unmarshal Qwen response: %v", err) return types.ActionContinue, fmt.Errorf("unable to unmarshal Qwen response: %v", err)
@@ -272,6 +320,15 @@ func (m *qwenProvider) OnResponseBody(ctx wrapper.HttpContext, apiName ApiName,
return types.ActionContinue, replaceJsonResponseBody(response, log) return types.ActionContinue, replaceJsonResponseBody(response, log)
} }
func (m *qwenProvider) onEmbeddingsResponseBody(ctx wrapper.HttpContext, body []byte, log wrapper.Log) (types.Action, error) {
qwenResponse := &qwenTextEmbeddingResponse{}
if err := json.Unmarshal(body, qwenResponse); err != nil {
return types.ActionContinue, fmt.Errorf("unable to unmarshal Qwen response: %v", err)
}
response := m.buildEmbeddingsResponse(ctx, qwenResponse)
return types.ActionContinue, replaceJsonResponseBody(response, log)
}
func (m *qwenProvider) buildQwenTextGenerationRequest(origRequest *chatCompletionRequest, streaming bool) *qwenTextGenRequest { func (m *qwenProvider) buildQwenTextGenerationRequest(origRequest *chatCompletionRequest, streaming bool) *qwenTextGenRequest {
messages := make([]qwenMessage, 0, len(origRequest.Messages)) messages := make([]qwenMessage, 0, len(origRequest.Messages))
for i := range origRequest.Messages { for i := range origRequest.Messages {
@@ -324,11 +381,11 @@ func (m *qwenProvider) buildChatCompletionResponse(ctx wrapper.HttpContext, qwen
return &chatCompletionResponse{ return &chatCompletionResponse{
Id: qwenResponse.RequestId, Id: qwenResponse.RequestId,
Created: time.Now().UnixMilli() / 1000, Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string), Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
SystemFingerprint: "", SystemFingerprint: "",
Object: objectChatCompletion, Object: objectChatCompletion,
Choices: choices, Choices: choices,
Usage: chatCompletionUsage{ Usage: usage{
PromptTokens: qwenResponse.Usage.InputTokens, PromptTokens: qwenResponse.Usage.InputTokens,
CompletionTokens: qwenResponse.Usage.OutputTokens, CompletionTokens: qwenResponse.Usage.OutputTokens,
TotalTokens: qwenResponse.Usage.TotalTokens, TotalTokens: qwenResponse.Usage.TotalTokens,
@@ -340,7 +397,7 @@ func (m *qwenProvider) buildChatCompletionStreamingResponse(ctx wrapper.HttpCont
baseMessage := chatCompletionResponse{ baseMessage := chatCompletionResponse{
Id: qwenResponse.RequestId, Id: qwenResponse.RequestId,
Created: time.Now().UnixMilli() / 1000, Created: time.Now().UnixMilli() / 1000,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string), Model: ctx.GetStringContext(ctxKeyFinalRequestModel, ""),
Choices: make([]chatCompletionChoice, 0), Choices: make([]chatCompletionChoice, 0),
SystemFingerprint: "", SystemFingerprint: "",
Object: objectChatCompletionChunk, Object: objectChatCompletionChunk,
@@ -396,10 +453,11 @@ func (m *qwenProvider) buildChatCompletionStreamingResponse(ctx wrapper.HttpCont
if finished { if finished {
finishResponse := *&baseMessage finishResponse := *&baseMessage
finishResponse.Choices = append(finishResponse.Choices, chatCompletionChoice{FinishReason: qwenChoice.FinishReason}) finishResponse.Choices = append(finishResponse.Choices, chatCompletionChoice{Delta: &chatMessage{}, FinishReason: qwenChoice.FinishReason})
usageResponse := *&baseMessage usageResponse := *&baseMessage
usageResponse.Usage = chatCompletionUsage{ usageResponse.Choices = []chatCompletionChoice{{Delta: &chatMessage{}}}
usageResponse.Usage = usage{
PromptTokens: qwenResponse.Usage.InputTokens, PromptTokens: qwenResponse.Usage.InputTokens,
CompletionTokens: qwenResponse.Usage.OutputTokens, CompletionTokens: qwenResponse.Usage.OutputTokens,
TotalTokens: qwenResponse.Usage.TotalTokens, TotalTokens: qwenResponse.Usage.TotalTokens,
@@ -484,6 +542,50 @@ func (m *qwenProvider) appendStreamEvent(responseBuilder *strings.Builder, event
responseBuilder.WriteString("\n\n") responseBuilder.WriteString("\n\n")
} }
func (m *qwenProvider) buildQwenTextEmbeddingRequest(request *embeddingsRequest) (*qwenTextEmbeddingRequest, error) {
var texts []string
if str, isString := request.Input.(string); isString {
texts = []string{str}
} else if strs, isArray := request.Input.([]interface{}); isArray {
texts = make([]string, 0, len(strs))
for _, item := range strs {
if str, isString := item.(string); isString {
texts = append(texts, str)
} else {
return nil, errors.New("unsupported input type in array: " + reflect.TypeOf(item).String())
}
}
} else {
return nil, errors.New("unsupported input type: " + reflect.TypeOf(request.Input).String())
}
return &qwenTextEmbeddingRequest{
Model: request.Model,
Input: qwenTextEmbeddingInput{
Texts: texts,
},
}, nil
}
func (m *qwenProvider) buildEmbeddingsResponse(ctx wrapper.HttpContext, qwenResponse *qwenTextEmbeddingResponse) *embeddingsResponse {
data := make([]embedding, 0, len(qwenResponse.Output.Embeddings))
for _, qwenEmbedding := range qwenResponse.Output.Embeddings {
data = append(data, embedding{
Object: "embedding",
Index: qwenEmbedding.TextIndex,
Embedding: qwenEmbedding.Embedding,
})
}
return &embeddingsResponse{
Object: "list",
Data: data,
Model: ctx.GetContext(ctxKeyFinalRequestModel).(string),
Usage: usage{
PromptTokens: qwenResponse.Usage.TotalTokens,
TotalTokens: qwenResponse.Usage.TotalTokens,
},
}
}
type qwenTextGenRequest struct { type qwenTextGenRequest struct {
Model string `json:"model"` Model string `json:"model"`
Input qwenTextGenInput `json:"input"` Input qwenTextGenInput `json:"input"`
@@ -510,7 +612,7 @@ type qwenTextGenParameters struct {
type qwenTextGenResponse struct { type qwenTextGenResponse struct {
RequestId string `json:"request_id"` RequestId string `json:"request_id"`
Output qwenTextGenOutput `json:"output"` Output qwenTextGenOutput `json:"output"`
Usage qwenTextGenUsage `json:"usage"` Usage qwenUsage `json:"usage"`
} }
type qwenTextGenOutput struct { type qwenTextGenOutput struct {
@@ -523,7 +625,7 @@ type qwenTextGenChoice struct {
Message qwenMessage `json:"message"` Message qwenMessage `json:"message"`
} }
type qwenTextGenUsage struct { type qwenUsage struct {
InputTokens int `json:"input_tokens"` InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"` OutputTokens int `json:"output_tokens"`
TotalTokens int `json:"total_tokens"` TotalTokens int `json:"total_tokens"`
@@ -536,6 +638,36 @@ type qwenMessage struct {
ToolCalls []toolCall `json:"tool_calls,omitempty"` ToolCalls []toolCall `json:"tool_calls,omitempty"`
} }
type qwenTextEmbeddingRequest struct {
Model string `json:"model"`
Input qwenTextEmbeddingInput `json:"input"`
Parameters qwenTextEmbeddingParameters `json:"parameters,omitempty"`
}
type qwenTextEmbeddingInput struct {
Texts []string `json:"texts"`
}
type qwenTextEmbeddingParameters struct {
TextType string `json:"text_type,omitempty"`
}
type qwenTextEmbeddingResponse struct {
RequestId string `json:"request_id"`
Output qwenTextEmbeddingOutput `json:"output"`
Usage qwenUsage `json:"usage"`
}
type qwenTextEmbeddingOutput struct {
RequestId string `json:"request_id"`
Embeddings []qwenTextEmbeddings `json:"embeddings"`
}
type qwenTextEmbeddings struct {
TextIndex int `json:"text_index"`
Embedding []float64 `json:"embedding"`
}
func qwenMessageToChatMessage(qwenMessage qwenMessage) chatMessage { func qwenMessageToChatMessage(qwenMessage qwenMessage) chatMessage {
return chatMessage{ return chatMessage{
Name: qwenMessage.Name, Name: qwenMessage.Name,

View File

@@ -43,14 +43,8 @@ func (m *stepfunProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiN
} }
_ = util.OverwriteRequestPath(stepfunChatCompletionPath) _ = util.OverwriteRequestPath(stepfunChatCompletionPath)
_ = util.OverwriteRequestHost(stepfunDomain) _ = util.OverwriteRequestHost(stepfunDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
if m.contextCache == nil {
ctx.DontReadRequestBody()
} else {
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
}
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -71,11 +65,11 @@ func (m *stepfunProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.stepfun.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.stepfun.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -43,14 +43,8 @@ func (m *yiProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName,
} }
_ = util.OverwriteRequestPath(yiChatCompletionPath) _ = util.OverwriteRequestPath(yiChatCompletionPath)
_ = util.OverwriteRequestHost(yiDomain) _ = util.OverwriteRequestHost(yiDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
if m.contextCache == nil {
ctx.DontReadRequestBody()
} else {
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
}
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -71,11 +65,11 @@ func (m *yiProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName, bod
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.yi.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.yi.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -42,14 +42,8 @@ func (m *zhipuAiProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiN
} }
_ = util.OverwriteRequestPath(zhipuAiChatCompletionPath) _ = util.OverwriteRequestPath(zhipuAiChatCompletionPath)
_ = util.OverwriteRequestHost(zhipuAiDomain) _ = util.OverwriteRequestHost(zhipuAiDomain)
_ = proxywasm.ReplaceHttpRequestHeader("Authorization", "Bearer "+m.config.GetRandomToken()) _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken())
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
if m.contextCache == nil {
ctx.DontReadRequestBody()
} else {
_ = proxywasm.RemoveHttpRequestHeader("Content-Length")
}
return types.ActionContinue, nil return types.ActionContinue, nil
} }
@@ -70,11 +64,11 @@ func (m *zhipuAiProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName
}() }()
if err != nil { if err != nil {
log.Errorf("failed to load context file: %v", err) log.Errorf("failed to load context file: %v", err)
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err)) _ = util.SendResponse(500, "ai-proxy.zhihupai.load_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to load context file: %v", err))
} }
insertContextMessage(request, content) insertContextMessage(request, content)
if err := replaceJsonRequestBody(request, log); err != nil { if err := replaceJsonRequestBody(request, log); err != nil {
_ = util.SendResponse(500, util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err)) _ = util.SendResponse(500, "ai-proxy.zhihupai.insert_ctx_failed", util.MimeTypeTextPlain, fmt.Sprintf("failed to replace request body: %v", err))
} }
}, log) }, log)
if err == nil { if err == nil {

View File

@@ -9,8 +9,8 @@ const (
MimeTypeApplicationJson = "application/json" MimeTypeApplicationJson = "application/json"
) )
func SendResponse(statusCode uint32, contentType, body string) error { func SendResponse(statusCode uint32, statusCodeDetails string, contentType, body string) error {
return proxywasm.SendHttpResponse(statusCode, CreateHeaders(HeaderContentType, contentType), []byte(body), -1) return proxywasm.SendHttpResponseWithDetail(statusCode, statusCodeDetails, CreateHeaders(HeaderContentType, contentType), []byte(body), -1)
} }
func CreateHeaders(kvs ...string) [][2]string { func CreateHeaders(kvs ...string) [][2]string {
@@ -34,3 +34,12 @@ func OverwriteRequestPath(path string) error {
} }
return proxywasm.ReplaceHttpRequestHeader(":path", path) return proxywasm.ReplaceHttpRequestHeader(":path", path)
} }
func OverwriteRequestAuthorization(credential string) error {
if exist, _ := proxywasm.GetHttpRequestHeader("X-HI-ORIGINAL-AUTH"); exist == "" {
if originAuth, err := proxywasm.GetHttpRequestHeader("Authorization"); err == nil {
_ = proxywasm.AddHttpRequestHeader("X-HI-ORIGINAL-AUTH", originAuth)
}
}
return proxywasm.ReplaceHttpRequestHeader("Authorization", credential)
}

View File

@@ -2,9 +2,11 @@ module ai-rag
go 1.18 go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.3.5 github.com/alibaba/higress/plugins/wasm-go v1.3.5
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

View File

@@ -7,6 +7,7 @@ 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/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 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-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -0,0 +1 @@
EXTRA_TAGS=proxy_wasm_version_0_2_100

View File

@@ -2,9 +2,11 @@ module myplugin
go 1.18 go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240522012622-fc6a6aad8906 github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240522012622-fc6a6aad8906
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

View File

@@ -9,6 +9,7 @@ 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/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 h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -145,8 +145,9 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body []
Message: respAdvice.Array()[0].Get("Answer").String(), Message: respAdvice.Array()[0].Get("Answer").String(),
} }
jsonData, _ := json.MarshalIndent(sr, "", " ") jsonData, _ := json.MarshalIndent(sr, "", " ")
proxywasm.SetProperty([]string{"risklabel"}, []byte(respResult.Array()[0].Get("Label").String())) label := respResult.Array()[0].Get("Label").String()
proxywasm.SendHttpResponse(403, [][2]string{{"content-type", "application/json"}}, jsonData, -1) proxywasm.SetProperty([]string{"risklabel"}, []byte(label))
proxywasm.SendHttpResponseWithDetail(403, "ai-security-guard.label."+label, [][2]string{{"content-type", "application/json"}}, jsonData, -1)
} else if respResult.Array()[0].Get("Label").String() != "nonLabel" { } else if respResult.Array()[0].Get("Label").String() != "nonLabel" {
sr := StandardResponse{ sr := StandardResponse{
Code: 403, Code: 403,
@@ -155,7 +156,7 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body []
} }
jsonData, _ := json.MarshalIndent(sr, "", " ") jsonData, _ := json.MarshalIndent(sr, "", " ")
proxywasm.SetProperty([]string{"risklabel"}, []byte(respResult.Array()[0].Get("Label").String())) proxywasm.SetProperty([]string{"risklabel"}, []byte(respResult.Array()[0].Get("Label").String()))
proxywasm.SendHttpResponse(403, [][2]string{{"content-type", "application/json"}}, jsonData, -1) proxywasm.SendHttpResponseWithDetail(403, "ai-security-guard.risk_detected", [][2]string{{"content-type", "application/json"}}, jsonData, -1)
} else { } else {
proxywasm.ResumeHttpRequest() proxywasm.ResumeHttpRequest()
} }

View File

@@ -2,9 +2,11 @@ module ai-statistics
go 1.18 go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240522012622-fc6a6aad8906 github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240522012622-fc6a6aad8906
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

View File

@@ -7,6 +7,7 @@ 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/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 h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"strings" "strings"
@@ -52,79 +53,66 @@ func onHttpResponseHeaders(ctx wrapper.HttpContext, config AIStatisticsConfig, l
return types.ActionContinue return types.ActionContinue
} }
func getLastChunk(data []byte) []byte { func onHttpStreamingBody(ctx wrapper.HttpContext, config AIStatisticsConfig, data []byte, endOfStream bool, log wrapper.Log) []byte {
chunks := strings.Split(strings.TrimSpace(string(data)), "\n\n") model, inputToken, outputToken, ok := getUsage(data)
length := len(chunks) if !ok {
if length < 2 {
return data return data
} }
// ai-proxy append extra usage chunk setFilterStateData(model, inputToken, outputToken, log)
return []byte(chunks[length-1]) incrementCounter(config, model, inputToken, outputToken, log)
}
func onHttpStreamingBody(ctx wrapper.HttpContext, config AIStatisticsConfig, data []byte, endOfStream bool, log wrapper.Log) []byte {
lastChunk := getLastChunk(data)
modelObj := gjson.GetBytes(lastChunk, "model")
inputTokenObj := gjson.GetBytes(lastChunk, "usage.prompt_tokens")
outputTokenObj := gjson.GetBytes(lastChunk, "usage.completion_tokens")
if modelObj.Exists() && inputTokenObj.Exists() && outputTokenObj.Exists() {
ctx.SetContext("model", modelObj.String())
ctx.SetContext("input_token", inputTokenObj.Int())
ctx.SetContext("output_token", outputTokenObj.Int())
}
if endOfStream {
var route, cluster string
if raw, err := proxywasm.GetProperty([]string{"route_name"}); err == nil {
route = string(raw)
}
if raw, err := proxywasm.GetProperty([]string{"cluster_name"}); err == nil {
cluster = string(raw)
}
model, ok := ctx.GetContext("model").(string)
if !ok {
log.Error("Get model failed!")
return data
}
inputToken, ok := ctx.GetContext("input_token").(int64)
if !ok {
log.Error("Get input_token failed!")
return data
}
outputToken, ok := ctx.GetContext("output_token").(int64)
if !ok {
log.Error("Get output_token failed!")
return data
}
config.incrementCounter("route."+route+".upstream."+cluster+".model."+model+".input_token", uint64(inputToken), log)
config.incrementCounter("route."+route+".upstream."+cluster+".model."+model+".output_token", uint64(outputToken), log)
proxywasm.SetProperty([]string{"model"}, []byte(model))
proxywasm.SetProperty([]string{"input_token"}, []byte(fmt.Sprint(inputToken)))
proxywasm.SetProperty([]string{"output_token"}, []byte(fmt.Sprint(outputToken)))
}
return data return data
} }
func onHttpResponseBody(ctx wrapper.HttpContext, config AIStatisticsConfig, body []byte, log wrapper.Log) types.Action { func onHttpResponseBody(ctx wrapper.HttpContext, config AIStatisticsConfig, body []byte, log wrapper.Log) types.Action {
modeObj := gjson.GetBytes(body, "model") model, inputToken, outputToken, ok := getUsage(body)
inputTokenObj := gjson.GetBytes(body, "usage.prompt_tokens") if !ok {
outputTokenObj := gjson.GetBytes(body, "usage.completion_tokens")
if !modeObj.Exists() {
log.Error("Get model failed")
return types.ActionContinue return types.ActionContinue
} }
if !inputTokenObj.Exists() { setFilterStateData(model, inputToken, outputToken, log)
log.Error("Get input_token failed") incrementCounter(config, model, inputToken, outputToken, log)
return types.ActionContinue return types.ActionContinue
}
func getUsage(data []byte) (model string, inputTokenUsage int64, outputTokenUsage int64, ok bool) {
chunks := bytes.Split(bytes.TrimSpace(data), []byte("\n\n"))
for _, chunk := range chunks {
// the feature strings are used to identify the usage data, like:
// {"model":"gpt2","usage":{"prompt_tokens":1,"completion_tokens":1}}
if !bytes.Contains(chunk, []byte("prompt_tokens")) {
continue
}
if !bytes.Contains(chunk, []byte("completion_tokens")) {
continue
}
modelObj := gjson.GetBytes(chunk, "model")
inputTokenObj := gjson.GetBytes(chunk, "usage.prompt_tokens")
outputTokenObj := gjson.GetBytes(chunk, "usage.completion_tokens")
if modelObj.Exists() && inputTokenObj.Exists() && outputTokenObj.Exists() {
model = modelObj.String()
inputTokenUsage = inputTokenObj.Int()
outputTokenUsage = outputTokenObj.Int()
ok = true
return
}
} }
if !outputTokenObj.Exists() { return
log.Error("Get output_token failed") }
return types.ActionContinue
// setFilterData sets the input_token and output_token in the filter state.
// ai-token-ratelimit will use these values to calculate the total token usage.
func setFilterStateData(model string, inputToken int64, outputToken int64, log wrapper.Log) {
if e := proxywasm.SetProperty([]string{"model"}, []byte(model)); e != nil {
log.Errorf("failed to set model in filter state: %v", e)
} }
model := modeObj.String() if e := proxywasm.SetProperty([]string{"input_token"}, []byte(fmt.Sprintf("%d", inputToken))); e != nil {
inputToken := inputTokenObj.Int() log.Errorf("failed to set input_token in filter state: %v", e)
outputToken := outputTokenObj.Int() }
if e := proxywasm.SetProperty([]string{"output_token"}, []byte(fmt.Sprintf("%d", outputToken))); e != nil {
log.Errorf("failed to set output_token in filter state: %v", e)
}
}
func incrementCounter(config AIStatisticsConfig, model string, inputToken int64, outputToken int64, log wrapper.Log) {
var route, cluster string var route, cluster string
if raw, err := proxywasm.GetProperty([]string{"route_name"}); err == nil { if raw, err := proxywasm.GetProperty([]string{"route_name"}); err == nil {
route = string(raw) route = string(raw)
@@ -134,10 +122,4 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AIStatisticsConfig, body
} }
config.incrementCounter("route."+route+".upstream."+cluster+".model."+model+".input_token", uint64(inputToken), log) config.incrementCounter("route."+route+".upstream."+cluster+".model."+model+".input_token", uint64(inputToken), log)
config.incrementCounter("route."+route+".upstream."+cluster+".model."+model+".output_token", uint64(outputToken), log) config.incrementCounter("route."+route+".upstream."+cluster+".model."+model+".output_token", uint64(outputToken), log)
proxywasm.SetProperty([]string{"model"}, []byte(model))
proxywasm.SetProperty([]string{"input_token"}, []byte(fmt.Sprint(inputToken)))
proxywasm.SetProperty([]string{"output_token"}, []byte(fmt.Sprint(outputToken)))
return types.ActionContinue
} }

View File

@@ -2,9 +2,11 @@ module ai-token-ratelimit
go 1.18 go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.4.1-0.20240617024146-5f150179637c github.com/alibaba/higress/plugins/wasm-go v1.4.1-0.20240617024146-5f150179637c
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
github.com/wasilibs/go-re2 v1.5.3 github.com/wasilibs/go-re2 v1.5.3
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837 github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837

View File

@@ -1,5 +1,3 @@
github.com/alibaba/higress/plugins/wasm-go v1.4.1-0.20240617024146-5f150179637c h1:wKCSg4rYfwkZrMk7tYY7navjgcHCMZjcgFrCsjLQBmg=
github.com/alibaba/higress/plugins/wasm-go v1.4.1-0.20240617024146-5f150179637c/go.mod h1:10jQXKsYFUF7djs+Oy7t82f4dbie9pISfP9FJwpPLuk=
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56 h1:Wi5Tgn8K+jDcBYL+dIMS1+qXYH2r7tpRAyBgqrWfQtw= github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56 h1:Wi5Tgn8K+jDcBYL+dIMS1+qXYH2r7tpRAyBgqrWfQtw=
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56/go.mod h1:8BhOLuqtSuT5NZtZMwfvEibi09RO3u79uqfHZzfDTR4= github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56/go.mod h1:8BhOLuqtSuT5NZtZMwfvEibi09RO3u79uqfHZzfDTR4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -7,8 +5,8 @@ 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/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 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew= 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-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo= github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -114,6 +114,7 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config ClusterKeyRateLimitCon
resultArray := response.Array() resultArray := response.Array()
if len(resultArray) != 3 { if len(resultArray) != 3 {
log.Errorf("redis response parse error, response: %v", response) log.Errorf("redis response parse error, response: %v", response)
proxywasm.ResumeHttpRequest()
return return
} }
context := LimitContext{ context := LimitContext{
@@ -162,12 +163,7 @@ func onHttpStreamingBody(ctx wrapper.HttpContext, config ClusterKeyRateLimitConf
keys := []interface{}{limitRedisContext.key} keys := []interface{}{limitRedisContext.key}
args := []interface{}{limitRedisContext.count, limitRedisContext.window, inputToken + outputToken} args := []interface{}{limitRedisContext.count, limitRedisContext.window, inputToken + outputToken}
err = config.redisClient.Eval(ResponsePhaseFixedWindowScript, 1, keys, args, func(response resp.Value) { err = config.redisClient.Eval(ResponsePhaseFixedWindowScript, 1, keys, args, nil)
if response.Error() != nil {
log.Errorf("call Eval error: %v", response.Error())
}
proxywasm.ResumeHttpResponse()
})
if err != nil { if err != nil {
log.Errorf("redis call failed: %v", err) log.Errorf("redis call failed: %v", err)
return data return data
@@ -298,6 +294,6 @@ func getDownStreamIp(rule LimitRuleItem) (net.IP, error) {
func rejected(config ClusterKeyRateLimitConfig, context LimitContext) { func rejected(config ClusterKeyRateLimitConfig, context LimitContext) {
headers := make(map[string][]string) headers := make(map[string][]string)
headers[RateLimitResetHeader] = []string{strconv.Itoa(context.reset)} headers[RateLimitResetHeader] = []string{strconv.Itoa(context.reset)}
_ = proxywasm.SendHttpResponse( _ = proxywasm.SendHttpResponseWithDetail(
config.rejectedCode, reconvertHeaders(headers), []byte(config.rejectedMsg), -1) config.rejectedCode, "ai-token-ratelimit.rejected", reconvertHeaders(headers), []byte(config.rejectedMsg), -1)
} }

View File

@@ -0,0 +1 @@
EXTRA_TAGS=proxy_wasm_version_0_2_100

View File

@@ -2,9 +2,11 @@ module ai-transformer
go 1.18 go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.4.0 github.com/alibaba/higress/plugins/wasm-go v1.4.0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

View File

@@ -11,6 +11,7 @@ github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1
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-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
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 h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0 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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

View File

@@ -7,6 +7,7 @@ github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1
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-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-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/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= 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/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

View File

@@ -293,19 +293,19 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config BasicAuthConfig, log w
} }
func deniedNoBasicAuthData() types.Action { func deniedNoBasicAuthData() types.Action {
_ = proxywasm.SendHttpResponse(http.StatusUnauthorized, WWWAuthenticateHeader(protectionSpace), _ = proxywasm.SendHttpResponseWithDetail(http.StatusUnauthorized, "basic-auth.no_auth_data", WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by Basic Auth check. No Basic Authentication information found."), -1) []byte("Request denied by Basic Auth check. No Basic Authentication information found."), -1)
return types.ActionContinue return types.ActionContinue
} }
func deniedInvalidCredentials() types.Action { func deniedInvalidCredentials() types.Action {
_ = proxywasm.SendHttpResponse(http.StatusUnauthorized, WWWAuthenticateHeader(protectionSpace), _ = proxywasm.SendHttpResponseWithDetail(http.StatusUnauthorized, "basic-auth.bad_credential", WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by Basic Auth check. Invalid username and/or password."), -1) []byte("Request denied by Basic Auth check. Invalid username and/or password."), -1)
return types.ActionContinue return types.ActionContinue
} }
func deniedUnauthorizedConsumer() types.Action { func deniedUnauthorizedConsumer() types.Action {
_ = proxywasm.SendHttpResponse(http.StatusForbidden, WWWAuthenticateHeader(protectionSpace), _ = proxywasm.SendHttpResponseWithDetail(http.StatusForbidden, "basic-auth.unauthorized", WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by Basic Auth check. Unauthorized consumer."), -1) []byte("Request denied by Basic Auth check. Unauthorized consumer."), -1)
return types.ActionContinue return types.ActionContinue
} }

View File

@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v1.3.2 github.com/alibaba/higress/plugins/wasm-go v1.3.2
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
github.com/wasilibs/go-re2 v1.4.1 github.com/wasilibs/go-re2 v1.4.1

View File

@@ -8,6 +8,7 @@ github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1
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-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-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/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= 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/magefile/mage v1.15.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -95,7 +95,7 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, botDetectConfig config.BotDet
method := ctx.Method() method := ctx.Method()
if ok, rule := botDetectConfig.Process(ua); !ok { if ok, rule := botDetectConfig.Process(ua); !ok {
proxywasm.SendHttpResponse(botDetectConfig.BlockedCode, nil, []byte(botDetectConfig.BlockedMessage), -1) proxywasm.SendHttpResponseWithDetail(botDetectConfig.BlockedCode, "bot-detect.blocked", nil, []byte(botDetectConfig.BlockedMessage), -1)
log.Debugf("scheme:%s, host:%s, method:%s, path:%s user-agent:%s has been blocked by rule:%s", scheme, host, method, path, ua, rule) log.Debugf("scheme:%s, host:%s, method:%s, path:%s user-agent:%s has been blocked by rule:%s", scheme, host, method, path, ua, rule)
return types.ActionPause return types.ActionPause
} }

View File

@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
require ( require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0 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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
github.com/tidwall/gjson v1.14.3 github.com/tidwall/gjson v1.14.3
) )

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