Compare commits

..

105 Commits

Author SHA1 Message Date
澄潭
792b9b0ee5 rel: Release version 1.2.0 (#556) 2023-09-22 18:11:03 +08:00
澄潭
26ed9a6d93 Update VERSION 2023-09-22 16:21:34 +08:00
Xunzhuo
ed36a4989f feat: add hgctl manifest support (#554)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-09-22 15:51:55 +08:00
Kent Dong
f23e26374f feat: Use higress as the default gateway class for Gateway API integation (#555) 2023-09-22 15:50:55 +08:00
Xunzhuo
eb2934c084 feat: add hgctl dashboard support (#552)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-09-22 13:51:22 +08:00
Vikizhao
2da1c62c69 feat: support KnativeIngress (#524) 2023-09-22 09:32:02 +08:00
Erica Liu
fab734d39a fix: nacos client opened, but not been closed (#542) 2023-09-21 17:03:04 +08:00
rinfx
2393af5c85 更新waf插件,丰富规则命中时日志内容 (#537) 2023-09-21 15:42:18 +08:00
Xunzhuo
b142f51776 feat: opt hgctl install/uninstall/upgrade (#550)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-09-21 13:44:09 +08:00
Jun
587267a733 feat: add profile/install/uninstall/upgrade command (#538) 2023-09-21 11:48:32 +08:00
Kent Dong
a2078711f5 feat: Use istio to provide Gateway API support (#543) 2023-09-20 15:20:20 +08:00
Kent Dong
dc54c581f3 rel: Release version 1.1.2 (#545) 2023-09-20 11:27:36 +08:00
WeixinX
b47d74bce5 fix: Fix the image display in the E2E test README.md (#516) 2023-09-19 23:24:06 +08:00
Xunzhuo
8d8ad6d624 fix: remove non-existed namespaces and separate bases (#541)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-09-19 17:12:38 +08:00
Xunzhuo
8062625d75 feat: add features to conformance and do some refactors (#532)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-09-19 11:53:06 +08:00
WeixinX
54a8a906ae feat: Implement basic-auth WASM plugin using the Go SDK (#515) 2023-09-18 11:13:08 +08:00
Kent Dong
8659895a91 feat: Add update function to get-higress.sh (#523) 2023-09-18 11:06:37 +08:00
澄潭
dc3e496aa0 fix a concurrency issue of mcprbidge reconcile (#511) 2023-09-08 15:59:22 +08:00
Kent Dong
8747e1ddad feat: update get-higress.sh (#500) 2023-08-29 10:23:08 +08:00
船长
2b9e3a14c2 A plugin that implements token parsing and authentication function based on wasm-go (#485) 2023-08-27 15:57:15 +08:00
Jun
1051201e97 add consul cluster (#494) 2023-08-19 10:40:34 +08:00
Kent Dong
8b24a20651 feat: Update image tag management mechanisms (#495) 2023-08-19 10:40:04 +08:00
澄潭
02b98dc4d8 optimize makefile for buildx (#489) 2023-08-18 11:58:35 +08:00
Kent Dong
e96f9a078c feat: Update tags of gateway and pilot images to 1.1.1 (#493) 2023-08-18 11:58:15 +08:00
Kent Dong
8e7793c470 feat: Update get-higress.sh to include descriptions of file storage (#492) 2023-08-18 11:43:56 +08:00
澄潭
e224df6bb4 change docker buildx rule (#488) 2023-08-16 17:25:51 +08:00
澄潭
a6444af185 Update build-image-and-push.yaml 2023-08-16 16:46:31 +08:00
澄潭
8e28ae781d Update build-image-and-push.yaml 2023-08-16 16:43:02 +08:00
澄潭
d58e781900 Update build-image-and-push.yaml 2023-08-16 16:10:51 +08:00
澄潭
6861a78bb1 use buildx to build multi arch image (#487) 2023-08-16 10:37:05 +08:00
澄潭
c3789416d6 Update build-image-and-push.yaml 2023-08-15 15:14:48 +08:00
澄潭
1913508f5e Update build-image-and-push.yaml 2023-08-15 14:43:19 +08:00
Kent Dong
0eaa9389c9 fix: Always use the latest CRDs file when deploying helm chart to OSS (#486) 2023-08-15 09:41:41 +08:00
澄潭
8048436604 support multi arch image (#483) 2023-08-14 20:41:09 +08:00
xiezheng-XD
ec229e69ac Update Dockerfile.base (#473) 2023-08-14 13:35:07 +08:00
Qianglin Li
f8f8b41fa2 feat: support eureka registry (#464)
Signed-off-by: charlie <qianglin98@qq.com>
2023-08-14 11:26:39 +08:00
Kent Dong
c49c8f1ec2 rel: Prepare for release v1.1.1 (#477) 2023-08-11 15:16:19 +08:00
Qianglin Li
49269b4303 fix: routing priorities are not working properly (#472)
Signed-off-by: charlie <qianglin98@qq.com>
2023-08-11 09:55:33 +08:00
Kent Dong
35d5669b51 fix: Read service count returned from the list instead of the Count field (#476) 2023-08-10 09:26:00 +08:00
rinfx
1f154c59f1 update nottinygc to v0.3.0 (#467) 2023-08-07 19:58:40 +08:00
teijin
8c206a6456 support configuration domain suffix (#460) (#463)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2023-08-07 16:26:51 +08:00
rinfx
d307d0e755 update wasm go sdk to tinygo 0.28.1 (#462) 2023-08-07 13:35:45 +08:00
Jun
9b88c6bb40 Feat registry consul (#445)
Co-authored-by: johnlanni <zty98751@alibaba-inc.com>
2023-08-01 16:16:28 +08:00
Kent Dong
e5105a4d71 fix: Fix the incorrect default metrics port shown in the usage (#456) 2023-08-01 09:54:25 +08:00
Kent Dong
564c7d8193 feat: Add port parameters into the usage of get-higress.sh (#452) 2023-07-28 09:44:48 +08:00
Hinsteny Hisoka
3700ada7e6 Wasm cpp bazel (#446) 2023-07-26 10:55:56 +08:00
Hinsteny Hisoka
3b78a0eb62 fix: fix kubetype-gen for http2rpc wrong plural value issue (#447) 2023-07-26 10:24:53 +08:00
fsl
0620346761 feat: add higress base image (#423)
Signed-off-by: fengshunli <1171313930@qq.com>
2023-07-25 13:35:44 +08:00
罗泽轩
aa17e9598d fix go mod path in waf plugin (#440)
Signed-off-by: spacewander <spacewanderlzx@gmail.com>
2023-07-17 20:35:45 +08:00
澄潭
fa834634b7 enable PILOT_FILTER_GATEWAY_CLUSTER_CONFIG (#433) 2023-07-15 17:24:39 +08:00
zackzhangkai
4ff311e0fc fix args (#434)
Signed-off-by: zackzhangkai <zhangkaiamm@gmail.com>
2023-07-14 13:22:04 +00:00
co63oc
90b7f209e2 Update wasm.pb.go (#430) 2023-07-14 13:39:28 +08:00
co63oc
6e1dd5bbc8 Update values.yaml (#432) 2023-07-14 13:38:23 +08:00
澄潭
a392d0cf34 Update README_EN.md 2023-07-12 15:03:54 +08:00
澄潭
43034d7d61 Update README_EN.md 2023-07-12 14:36:59 +08:00
澄潭
8c76ae26bb update console version (#427) 2023-07-10 21:01:09 +08:00
Kent Dong
c1250aec2e fix: Fix the incompatibility issue with Windows using pipeline (#426) 2023-07-10 20:53:54 +08:00
Kent Dong
02bc319eef fix: Remove two debug logs (#424) 2023-07-10 20:16:05 +08:00
Kent Dong
28892cf3ae feat: Add a GitHub action to deploy standalone packages to OSS (#421) 2023-07-08 21:51:59 +08:00
澄潭
bee03a37a4 Release 1.1 (#412) 2023-07-07 17:53:44 +08:00
VIKI ZHAO
2a97921d2b AI proxy plugin (#420) 2023-07-07 15:30:04 +08:00
Jun
d4dbaba760 deepcopy tracing configuration and fix code format (#411) 2023-07-06 10:02:45 +08:00
纪卓志
d718870b65 feat: get_match_config returns with rule_id for stateful plugins (#406) 2023-07-04 19:59:43 +08:00
Jun
b65446fa25 add tracing in higress-config configmap (#409) 2023-07-04 19:59:11 +08:00
Xunzhuo
3fd37abab7 refactor: e2e infras (#407)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-06-30 17:28:10 +08:00
澄潭
81e467b624 support gateway in other ns (#405) 2023-06-29 11:00:02 +08:00
Hinsteny Hisoka
736eea6cf9 feat: Support dubbo group for http2rpc (#404) 2023-06-29 10:55:34 +08:00
rinfx
c32e1ab69b Go WAF Plugin (#400) 2023-06-28 19:25:36 +08:00
纪卓志
fc05a3b256 feat: support rewrite config after parse_rule_config invoked (#401) 2023-06-28 14:22:28 +08:00
澄潭
9fc2760b7d support higress-cn helm index (#399) 2023-06-28 09:42:29 +08:00
纪卓志
be88647752 feat: extract boilerplate code of on_configure to function (DRY) (#396) 2023-06-28 09:42:19 +08:00
澄潭
a56172095a fix debug configz (#398) 2023-06-27 16:42:38 +08:00
Woa
f3270123ba fix: split REGISTRY into BUILDER_REGISTRY and REGISTRY (#395) 2023-06-25 19:52:17 +08:00
Jun
5e2d62406b fix cond unlock (#394) 2023-06-25 09:39:42 +08:00
Kent Dong
39cab9d724 fix: Add docker image URLs to output (#391) 2023-06-21 14:02:32 +08:00
Kent Dong
89865733f6 feat: Support building and pushing Higress docker images via GitHub actions (#388) 2023-06-21 10:18:23 +08:00
Hinsteny Hisoka
1ccf9195b2 Add e2e testing description for wasm-plugin-go (#389) 2023-06-21 10:17:03 +08:00
澄潭
7d2a05ef1c Update README.md 2023-06-19 10:48:48 +08:00
纪卓志
32c2acefda feat: add wasm-rust sdk with say-hello simple extension (#350) 2023-06-19 10:40:53 +08:00
Hinsteny Hisoka
ea7b581e26 Support HTTP/JSON to dubbo (#340) 2023-06-19 10:40:28 +08:00
Kent Dong
ac2f0a5545 feat: Pilot debug APIs support remote anonymous requests (#386) 2023-06-19 09:36:52 +08:00
Kent Dong
51d7124454 release: Prepare for releasing v1.0.1 (#367) 2023-06-16 18:23:17 +08:00
Hinsteny Hisoka
ec6a185adc feat: support only build one plugin to testing specified WasmPlugin (#371)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2023-06-15 15:21:54 +08:00
Hinsteny Hisoka
f9ffda288b feature: e2e framework supports testing wasm plugins (#369) 2023-06-08 11:00:30 +08:00
Kent Dong
2dbe41324a fix: Fix some compatibility issues related to older versions of K8s (#363) 2023-06-06 18:02:47 +08:00
Qianglin Li
80d6ecfddb feat: support full path regex annotation (#286)
Signed-off-by: charlie <qianglin98@qq.com>
2023-06-02 18:56:22 +08:00
Ink33
b23fae7a12 ci: enable building files caching (#323)
Signed-off-by: Ink33 <Ink33@smlk.org>
Co-authored-by: Xunzhuo <bitliu@tencent.com>
2023-06-01 11:35:37 +08:00
WeixinX
2c19d97252 feat: Add e2e testcases for 'auth-tls-secret' and 'ssl-cipher' (#354) 2023-05-30 11:08:31 +08:00
Xunzhuo
efd7ccd5fe fix: e2e refers (#345) 2023-05-26 12:02:39 +08:00
Xunzhuo
81fd0d6386 fix: e2e refers (#345) 2023-05-26 12:01:50 +08:00
Jun
176ddc6963 Plugin cors (#349) 2023-05-25 19:21:21 +08:00
WeixinX
44637c2449 fix: Fix e2e 'request header control' and add some testcases (#352) 2023-05-25 15:51:39 +08:00
澄潭
3e68ae75d1 update version to 1.0.0 (#348) 2023-05-22 20:58:19 +08:00
Ffyyt
18ad817edb Add goproxy environment variable for build-image (#347) 2023-05-22 20:57:53 +08:00
Xunzhuo
d48e0ce773 e2e: rename some cases name to make it sync with others (#344) 2023-05-22 11:05:22 +08:00
Kent Dong
9734ffeb3e feat: Refactor skywalking configuration structure (#333) 2023-05-21 15:44:07 +08:00
Ffyyt
1421ce8667 e2e:add test for ssl redirect annotations (#341) 2023-05-21 15:20:00 +08:00
Jun
625c06e58f get upstream serviceSource from RouteCluster information and update docs (#337) 2023-05-19 10:40:24 +08:00
澄潭
e4a47dfb46 compatiable with tinygo 0.25 (#330) 2023-05-15 19:22:05 +08:00
WeixinX
6b483189ac docs: fix some plugin READMEs (#327) 2023-05-12 14:23:58 +08:00
刘晓瑞
74ad9a555a extend wasm go sdk with OnHttpStreamDone (#325) 2023-05-10 14:58:04 +08:00
Kent Dong
f6e181ecb6 fix: Sync Chart.lock file (#321) 2023-05-09 21:14:22 +08:00
Kent Dong
30a5b2ab2b feat: Upgrade Higress Console to v1.0.0-rc.2 (#320) 2023-05-09 17:24:15 +08:00
澄潭
91a23cc27e Update README_EN.md 2023-05-09 16:49:51 +08:00
澄潭
51e515d53e Update README.md 2023-05-09 16:49:10 +08:00
380 changed files with 48585 additions and 1185 deletions

View File

@@ -23,6 +23,28 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- run: git stash # restore patch
# test
- name: Run Coverage Tests
run: GOPROXY="https://proxy.golang.org,direct" make go.test.coverage
@@ -38,15 +60,37 @@ jobs:
runs-on: ubuntu-latest
needs: [lint,coverage-test]
steps:
- name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: "checkout ${{ github.ref }}"
uses: actions/checkout@v3
- name: Setup Golang Caches
uses: actions/cache@v3
with:
fetch-depth: 2
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- run: git stash # restore patch
- name: "Build Higress Binary"
run: GOPROXY="https://proxy.golang.org,direct" make build
@@ -63,20 +107,86 @@ jobs:
steps:
- uses: actions/checkout@v3
ingress-conformance-test:
higress-conformance-test:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v3
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- run: git stash # restore patch
- name: "Run Higress E2E Conformance Tests"
run: GOPROXY="https://proxy.golang.org,direct" make higress-conformance-test
higress-wasmplugin-test:
runs-on: ubuntu-latest
needs: [build]
strategy:
matrix:
# TODO(Xunzhuo): Enable C WASM Filters in CI
wasmPluginType: [ GO ]
steps:
- uses: actions/checkout@v3
- name: "Run Ingress Conformance Tests"
run: GOPROXY="https://proxy.golang.org,direct" make ingress-conformance-test
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- run: git stash # restore patch
- name: "Run Ingress WasmPlugins Tests"
run: GOPROXY="https://proxy.golang.org,direct" PLUGIN_TYPE=${{ matrix.wasmPluginType }} make higress-wasmplugin-test
publish:
runs-on: ubuntu-latest
needs: [ingress-conformance-test,gateway-conformance-test]
needs: [higress-conformance-test,gateway-conformance-test,higress-wasmplugin-test]
steps:
- uses: actions/checkout@v3

View File

@@ -0,0 +1,207 @@
name: Build Docker Images and Push to Image Registry
on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
build-controller-image:
runs-on: ubuntu-latest
environment:
name: image-registry-controller
env:
CONTROLLER_IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
CONTROLLER_IMAGE_NAME: ${{ vars.CONTROLLER_IMAGE_NAME || 'higress/higress' }}
steps:
- name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules-new
- name: Calculate Docker metadata
id: docker-meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.CONTROLLER_IMAGE_REGISTRY }}/${{ env.CONTROLLER_IMAGE_NAME }}
tags: |
type=sha
type=ref,event=tag
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: ${{ env.CONTROLLER_IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build Docker Image and Push
run: |
GOPROXY="https://proxy.golang.org,direct" make docker-buildx-push
BUILT_IMAGE="higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress"
readarray -t IMAGES <<< "${{ steps.docker-meta.outputs.tags }}"
for image in ${IMAGES[@]}; do
echo "Image: $image"
docker buildx imagetools create $BUILT_IMAGE:$GITHUB_SHA --tag $image
done
build-pilot-image:
runs-on: ubuntu-latest
environment:
name: image-registry-pilot
env:
PILOT_IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
PILOT_IMAGE_NAME: ${{ vars.PILOT_IMAGE_NAME || 'higress/pilot' }}
steps:
- name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules-new
- name: Calculate Docker metadata
id: docker-meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.PILOT_IMAGE_REGISTRY }}/${{ env.PILOT_IMAGE_NAME }}
tags: |
type=sha
type=ref,event=tag
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: ${{ env.PILOT_IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build Pilot-Discovery Image and Push
run: |
GOPROXY="https://proxy.golang.org,direct" make build-istio
BUILT_IMAGE="higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/pilot"
readarray -t IMAGES <<< "${{ steps.docker-meta.outputs.tags }}"
for image in ${IMAGES[@]}; do
echo "Image: $image"
docker buildx imagetools create $BUILT_IMAGE:$GITHUB_SHA --tag $image
done
build-gateway-image:
runs-on: ubuntu-latest
environment:
name: image-registry-pilot
env:
GATEWAY_IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
GATEWAY_IMAGE_NAME: ${{ vars.GATEWAY_IMAGE_NAME || 'higress/gateway' }}
steps:
- name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules-new
- name: Calculate Docker metadata
id: docker-meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.GATEWAY_IMAGE_REGISTRY }}/${{ env.GATEWAY_IMAGE_NAME }}
tags: |
type=sha
type=ref,event=tag
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: ${{ env.GATEWAY_IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build Gateway Image and Push
run: |
GOPROXY="https://proxy.golang.org,direct" make build-gateway
BUILT_IMAGE="higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/proxyv2"
readarray -t IMAGES <<< "${{ steps.docker-meta.outputs.tags }}"
for image in ${IMAGES[@]}; do
echo "Image: $image"
docker buildx imagetools create $BUILT_IMAGE:$GITHUB_SHA --tag $image
done

View File

@@ -0,0 +1,37 @@
name: Deploy Standalone to OSS
on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
deploy-to-oss:
runs-on: ubuntu-latest
environment:
name: oss
steps:
# Step 1
- name: Checkout
uses: actions/checkout@v3
# Step 2
- id: package
name: Prepare Standalone Package
run: |
mkdir ./artifact
cp ./tools/get-higress.sh ./artifact
LOCAL_RELEASE_URL="https://github.com/higress-group/higress-standalone/releases"
VERSION=$(curl -Ls $LOCAL_RELEASE_URL | grep 'href="/higress-group/higress-standalone/releases/tag/v[0-9]*.[0-9]*.[0-9]*\"' | sed -E 's/.*\/higress-group\/higress-standalone\/releases\/tag\/(v[0-9\.]+)".*/\1/g' | head -1)
DOWNLOAD_URL="https://github.com/higress-group/higress-standalone/archive/refs/tags/${VERSION}.tar.gz"
curl -SsL "$DOWNLOAD_URL" -o "./artifact/higress-${VERSION}.tar.gz"
echo -n "$VERSION" > ./artifact/VERSION
echo "Version=$VERSION"
# Step 3
- name: Upload to OSS
uses: doggycool/ossutil-github-action@master
with:
ossArgs: 'cp -r -u ./artifact/ oss://higress-website-cn-hongkong/standalone/'
accessKey: ${{ secrets.ACCESS_KEYID }}
accessSecret: ${{ secrets.ACCESS_KEYSECRET }}
endpoint: oss-cn-hongkong.aliyuncs.com

View File

@@ -4,10 +4,13 @@ on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
deploy-to-oss:
runs-on: ubuntu-latest
environment:
name: oss
steps:
# Step 1
- name: Checkout
@@ -33,11 +36,14 @@ jobs:
with:
helmv3: 3.7.2
command: |
cp api/kubernetes/customresourcedefinitions.gen.yaml helm/core/crds
helmv3 repo add higress.io https://higress.io/helm-charts
helmv3 package helm/core --debug --app-version ${{steps.calc-version.outputs.version}} --version ${{steps.calc-version.outputs.version}} -d ./artifact
helmv3 dependency build helm/higress
helmv3 package helm/higress --debug --app-version ${{steps.calc-version.outputs.version}} --version ${{steps.calc-version.outputs.version}} -d ./artifact
helmv3 repo index --url https://higress.io/helm-charts/ --merge ./artifact/index.yaml ./artifact
cp ./artifact/index.yaml ./artifact/cn-index.yaml
sed -i 's/higress\.io/higress\.cn/g' ./artifact/cn-index.yaml
# Step 5
- name: Upload to OSS
uses: doggycool/ossutil-github-action@master

4
.gitignore vendored
View File

@@ -12,4 +12,6 @@ bazel-out
bazel-testlogs
bazel-wasm-cpp
tools/bin/
helm/**/charts/**.tgz
helm/**/charts/**.tgz
target/
tools/hack/cluster.conf

View File

@@ -17,6 +17,8 @@ GO ?= go
export GOPROXY ?= https://proxy.golang.com.cn,direct
TARGET_ARCH ?= amd64
GOARCH_LOCAL := $(TARGET_ARCH)
GOOS_LOCAL := $(TARGET_OS)
RELEASE_LDFLAGS='$(GO_LDFLAGS) -extldflags -static -s -w'
@@ -24,6 +26,8 @@ RELEASE_LDFLAGS='$(GO_LDFLAGS) -extldflags -static -s -w'
export OUT:=$(TARGET_OUT)
export OUT_LINUX:=$(TARGET_OUT_LINUX)
BUILDX_PLATFORM ?=
# If tag not explicitly set in users' .istiorc.mk or command line, default to the git sha.
TAG ?= $(shell git rev-parse --verify HEAD)
ifeq ($(TAG),)
@@ -49,6 +53,7 @@ $(OUT):
submodule:
git submodule update --init
.PHONY: prebuild
prebuild: submodule
./tools/hack/prebuild.sh
@@ -67,6 +72,12 @@ build: prebuild $(OUT)
build-linux: prebuild $(OUT)
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT_LINUX)/ $(HIGRESS_BINARIES)
$(AMD64_OUT_LINUX)/higress:
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=amd64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_amd64/ $(HIGRESS_BINARIES)
$(ARM64_OUT_LINUX)/higress:
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=arm64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_arm64/ $(HIGRESS_BINARIES)
.PHONY: build-hgctl
build-hgctl: $(OUT)
GOPROXY=$(GOPROXY) GOOS=$(GOOS_LOCAL) GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT)/ $(HGCTL_BINARIES)
@@ -114,19 +125,43 @@ include docker/docker.mk
docker-build: docker.higress ## Build and push docker images to registry defined by $HUB and $TAG
docker-buildx-push: clean-env docker.higress-buildx
docker-build-base:
docker buildx build --no-cache --platform linux/amd64,linux/arm64 -t ${HUB}/base:${BASE_VERSION} -f docker/Dockerfile.base . --push
export PARENT_GIT_TAG:=$(shell cat VERSION)
export PARENT_GIT_REVISION:=$(TAG)
export ENVOY_TAR_PATH:=/home/package/envoy.tar.gz
external/package/envoy.tar.gz:
cd external/proxy; BUILD_WITH_CONTAINER=1 make test_release
external/package/envoy-amd64.tar.gz:
# cd external/proxy; BUILD_WITH_CONTAINER=1 make test_release
cd external/package; wget "https://github.com/alibaba/higress/releases/download/v1.0.0/envoy-amd64.tar.gz"
build-gateway: prebuild external/package/envoy.tar.gz
cd external/istio; rm -rf out; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.proxyv2" make docker
external/package/envoy-arm64.tar.gz:
# cd external/proxy; BUILD_WITH_CONTAINER=1 make test_release
cd external/package; wget "https://github.com/alibaba/higress/releases/download/v1.0.0/envoy-arm64.tar.gz"
build-istio: prebuild
cd external/istio; rm -rf out; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.pilot" make docker
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_arm64; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=arm64 BUILD_WITH_CONTAINER=1 make build-linux
build-gateway: prebuild external/package/envoy-amd64.tar.gz external/package/envoy-arm64.tar.gz build-pilot
cd external/istio; BUILD_WITH_CONTAINER=1 BUILDX_PLATFORM=true DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.proxyv2" make docker
build-gateway-local: prebuild external/package/envoy-amd64.tar.gz external/package/envoy-arm64.tar.gz build-pilot
cd external/istio; BUILD_WITH_CONTAINER=1 BUILDX_PLATFORM=false DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.proxyv2" make docker
build-istio: prebuild build-pilot
cd external/istio; BUILD_WITH_CONTAINER=1 BUILDX_PLATFORM=true DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.pilot" make docker
build-istio-local: prebuild build-pilot
cd external/istio; BUILD_WITH_CONTAINER=1 BUILDX_PLATFORM=false DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.pilot" make docker
build-wasmplugins:
./tools/hack/build-wasm-plugins.sh
pre-install:
cp api/kubernetes/customresourcedefinitions.gen.yaml helm/core/crds
@@ -139,11 +174,13 @@ install: pre-install
cd helm/higress; helm dependency build
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
ENVOY_LATEST_IMAGE_TAG ?= 1.0.0-rc
ISTIO_LATEST_IMAGE_TAG ?= 1.0.0-rc
ENVOY_LATEST_IMAGE_TAG ?= 1.2.0
ISTIO_LATEST_IMAGE_TAG ?= 1.2.0
install-dev: pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --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'
install-dev-wasmplugin: build-wasmplugins 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' --set 'global.volumeWasmPlugins=true'
uninstall:
helm uninstall higress -n higress-system
@@ -193,9 +230,13 @@ include tools/lint.mk
.PHONY: gateway-conformance-test
gateway-conformance-test:
# ingress-conformance-test runs ingress api conformance tests.
.PHONY: ingress-conformance-test
ingress-conformance-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev run-ingress-e2e-test delete-cluster
# higress-conformance-test runs ingress api conformance tests.
.PHONY: higress-conformance-test
higress-conformance-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev run-higress-e2e-test delete-cluster
# higress-wasmplugin-test runs ingress wasmplugin tests.
.PHONY: higress-wasmplugin-test
higress-wasmplugin-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev-wasmplugin run-higress-e2e-test-wasmplugin delete-cluster
# create-cluster creates a kube cluster with kind.
.PHONY: create-cluster
@@ -208,16 +249,40 @@ delete-cluster: $(tools/kind) ## Delete kind cluster.
$(tools/kind) delete cluster --name higress
# kube-load-image loads a local built docker image into kube cluster.
# dubbo-provider-demo和nacos-standlone-rc3的镜像已经上传到阿里云镜像库第一次需要先拉到本地
# docker pull registry.cn-hangzhou.aliyuncs.com/hinsteny/dubbo-provider-demo:0.0.1
# docker pull registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3:1.0.0-RC3
.PHONY: kube-load-image
kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster using the provided $IMAGE and $TAG.
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress $(TAG)
# run-ingress-e2e-test starts to run ingress e2e tests.
.PHONY: run-ingress-e2e-test
run-ingress-e2e-test:
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/dubbo-provider-demo 0.0.1
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3
tools/hack/docker-pull-image.sh docker.io/hashicorp/consul 1.16.0
tools/hack/docker-pull-image.sh docker.io/charlie1380/eureka-registry-provider v0.3.0
tools/hack/docker-pull-image.sh docker.io/bitinit/eureka latest
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin 1.0.2
tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/dubbo-provider-demo 0.0.1
tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3
tools/hack/kind-load-image.sh docker.io/hashicorp/consul 1.16.0
tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin 1.0.2
tools/hack/kind-load-image.sh docker.io/charlie1380/eureka-registry-provider v0.3.0
tools/hack/kind-load-image.sh docker.io/bitinit/eureka latest
# run-higress-e2e-test starts to run ingress e2e tests.
.PHONY: run-higress-e2e-test
run-higress-e2e-test:
@echo -e "\n\033[36mRunning higress conformance tests...\033[0m"
@echo -e "\n\033[36mWaiting higress-controller to be ready...\033[0m\n"
kubectl wait --timeout=10m -n higress-system deployment/higress-controller --for=condition=Available
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
kubectl wait --timeout=10m -n higress-system deployment/higress-gateway --for=condition=Available
go test -v -tags conformance ./test/ingress/e2e_test.go --ingress-class=higress --debug=true
go test -v -tags conformance ./test/e2e/e2e_test.go --ingress-class=higress --debug=true
# run-higress-e2e-test starts to run ingress e2e tests.
.PHONY: run-higress-e2e-test-wasmplugin
run-higress-e2e-test-wasmplugin:
@echo -e "\n\033[36mRunning higress conformance tests...\033[0m"
@echo -e "\n\033[36mWaiting higress-controller to be ready...\033[0m\n"
kubectl wait --timeout=10m -n higress-system deployment/higress-controller --for=condition=Available
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
kubectl wait --timeout=10m -n higress-system deployment/higress-gateway --for=condition=Available
go test -v -tags conformance ./test/e2e/e2e_test.go -isWasmPluginTest=true -wasmPluginType=$(PLUGIN_TYPE) -wasmPluginName=$(PLUGIN_NAME) --ingress-class=higress --debug=true

View File

@@ -21,7 +21,7 @@
Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源 [Istio](https://github.com/istio/istio) 与 [Envoy](https://github.com/envoyproxy/envoy) 为核心构建的下一代云原生网关。Higress 实现了安全防护网关、流量网关、微服务网关三层网关合一,可以显著降低网关的部署和运维成本。
![arch](https://img.alicdn.com/imgextra/i4/O1CN01OgGP1728t0xeRfRYJ_!!6000000007989-0-tps-1726-1366.jpg)
![arch](https://img.alicdn.com/imgextra/i1/O1CN01iO9ph825juHbOIg75_!!6000000007563-2-tps-2483-2024.png)
## Summary
@@ -125,7 +125,7 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
社区交流群:
![image](https://img.alicdn.com/imgextra/i1/O1CN01d7LmWu1rMB71rfRhA_!!6000000005616-2-tps-720-405.png)
![image](https://img.alicdn.com/imgextra/i1/O1CN01KWonlE1DkpqaYVTiC_!!6000000000255-0-tps-720-405.jpg)
开发者群:

View File

@@ -23,7 +23,7 @@ Higress is a next-generation cloud-native gateway based on Alibaba's internal ga
Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.com/envoyproxy/envoy), Higress realizes the integration of the triple gateway architecture of traffic gateway, microservice gateway and security gateway, thereby greatly reducing the costs of deployment, operation and maintenance.
<h1 align="center">
<img src="https://img.alicdn.com/imgextra/i1/O1CN01vnNawh26mU5C9py9w_!!6000000007704-0-tps-1726-1366.jpg" alt="Higress Architecture">
<img src="https://img.alicdn.com/imgextra/i1/O1CN01iO9ph825juHbOIg75_!!6000000007563-2-tps-2483-2024.png" alt="Higress Architecture">
</h1>
@@ -32,6 +32,7 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
- [**Use Cases**](#use-cases)
- [**Higress Features**](#higress-features)
- [**Quick Start**](https://higress.io/en-us/docs/user/quickstart)
- [**Community**](#community)
- [**Thanks**](#thanks)
## Use Cases
@@ -74,6 +75,10 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co
Provides JWT, OIDC, custom authentication and authentication, deeply integrates open source web application firewall.
## Community
[Slack](https://w1689142780-euk177225.slack.com/archives/C05GEL4TGTG): to get invited go [here](https://communityinviter.com/apps/w1689142780-euk177225/higress).
### Thanks
Higress would not be possible without the valuable open-source work of projects in the community. We would like to extend a special thank-you to Envoy and Istio.

View File

@@ -1 +1 @@
v1.0.0-rc
v1.2.0

View File

@@ -171,7 +171,7 @@ type WasmPlugin struct {
DefaultConfig *types.Struct `protobuf:"bytes,101,opt,name=default_config,json=defaultConfig,proto3" json:"default_config,omitempty"`
// Extended by Higress, matching rules take effect
MatchRules []*MatchRule `protobuf:"bytes,102,rep,name=match_rules,json=matchRules,proto3" json:"match_rules,omitempty"`
// diable the default config
// disable the default config
DefaultConfigDisable bool `protobuf:"varint,103,opt,name=default_config_disable,json=defaultConfigDisable,proto3" json:"default_config_disable,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`

View File

@@ -104,7 +104,7 @@ message WasmPlugin {
google.protobuf.Struct default_config = 101;
// Extended by Higress, matching rules take effect
repeated MatchRule match_rules = 102;
// diable the default config
// disable the default config
bool default_config_disable = 103;
}

View File

@@ -104,6 +104,88 @@ spec:
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
"helm.sh/resource-policy": keep
name: http2rpcs.networking.higress.io
spec:
group: networking.higress.io
names:
categories:
- higress-io
kind: Http2Rpc
listKind: Http2RpcList
plural: http2rpcs
singular: http2rpc
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
properties:
spec:
oneOf:
- not:
anyOf:
- required:
- dubbo
- required:
- grpc
- required:
- dubbo
- required:
- grpc
properties:
dubbo:
properties:
group:
type: string
methods:
items:
properties:
headersAttach:
type: string
httpMethods:
items:
type: string
type: array
httpPath:
type: string
params:
items:
properties:
paramKey:
type: string
paramSource:
type: string
paramType:
type: string
type: object
type: array
serviceMethod:
type: string
type: object
type: array
service:
type: string
version:
type: string
type: object
grpc:
type: object
type: object
status:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
@@ -131,8 +213,17 @@ spec:
registries:
items:
properties:
authSecretName:
type: string
consulDatacenter:
type: string
consulNamespace:
type: string
consulRefreshInterval:
format: int64
type: integer
consulServiceTag:
type: string
domain:
type: string
nacosAccessKey:

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
syntax = "proto3";
import "google/api/field_behavior.proto";
// $schema: higress.networking.v1.Http2Rpc
// $title: Http2Rpc
// $description: Configuration affecting service discovery from multi registries
// $mode: none
package higress.networking.v1;
option go_package = "github.com/alibaba/higress/api/networking/v1";
// <!-- crd generation tags
// +cue-gen:Http2Rpc:groupName:networking.higress.io
// +cue-gen:Http2Rpc:version:v1
// +cue-gen:Http2Rpc:storageVersion
// +cue-gen:Http2Rpc:annotations:helm.sh/resource-policy=keep
// +cue-gen:Http2Rpc:subresource:status
// +cue-gen:Http2Rpc:scope:Namespaced
// +cue-gen:Http2Rpc:resource:categories=higress-io,plural=http2rpcs
// +cue-gen:Http2Rpc:preserveUnknownFields:false
// -->
//
// <!-- go code generation tags
// +kubetype-gen
// +kubetype-gen:groupVersion=networking.higress.io/v1
// +genclient
// +k8s:deepcopy-gen=true
// -->
message Http2Rpc {
oneof destination {
DubboService dubbo = 1;
GrpcService grpc = 2;
}
}
message DubboService {
string service = 1 [(google.api.field_behavior) = REQUIRED];
string version = 2 [(google.api.field_behavior) = REQUIRED];
string group = 3 [(google.api.field_behavior) = OPTIONAL];
repeated Method methods = 4 [(google.api.field_behavior) = REQUIRED];
}
message Method {
string service_method = 1 [(google.api.field_behavior) = REQUIRED];
string headers_attach = 2 [(google.api.field_behavior) = OPTIONAL];
string http_path = 3 [(google.api.field_behavior) = REQUIRED];
repeated string http_methods = 4 [(google.api.field_behavior) = REQUIRED];
repeated Param params = 5;
}
message Param {
string param_source = 1 [(google.api.field_behavior) = REQUIRED];
string param_key = 2 [(google.api.field_behavior) = REQUIRED];
string param_type = 3 [(google.api.field_behavior) = REQUIRED];
}
message GrpcService {
}

View File

@@ -0,0 +1,121 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: networking/v1/http_2_rpc.proto
package v1
import (
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
_ "istio.io/gogo-genproto/googleapis/google/api"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// DeepCopyInto supports using Http2Rpc within kubernetes types, where deepcopy-gen is used.
func (in *Http2Rpc) DeepCopyInto(out *Http2Rpc) {
p := proto.Clone(in).(*Http2Rpc)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Http2Rpc. Required by controller-gen.
func (in *Http2Rpc) DeepCopy() *Http2Rpc {
if in == nil {
return nil
}
out := new(Http2Rpc)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Http2Rpc. Required by controller-gen.
func (in *Http2Rpc) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using DubboService within kubernetes types, where deepcopy-gen is used.
func (in *DubboService) DeepCopyInto(out *DubboService) {
p := proto.Clone(in).(*DubboService)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DubboService. Required by controller-gen.
func (in *DubboService) DeepCopy() *DubboService {
if in == nil {
return nil
}
out := new(DubboService)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new DubboService. Required by controller-gen.
func (in *DubboService) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using Method within kubernetes types, where deepcopy-gen is used.
func (in *Method) DeepCopyInto(out *Method) {
p := proto.Clone(in).(*Method)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Method. Required by controller-gen.
func (in *Method) DeepCopy() *Method {
if in == nil {
return nil
}
out := new(Method)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Method. Required by controller-gen.
func (in *Method) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using Param within kubernetes types, where deepcopy-gen is used.
func (in *Param) DeepCopyInto(out *Param) {
p := proto.Clone(in).(*Param)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Param. Required by controller-gen.
func (in *Param) DeepCopy() *Param {
if in == nil {
return nil
}
out := new(Param)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Param. Required by controller-gen.
func (in *Param) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using GrpcService within kubernetes types, where deepcopy-gen is used.
func (in *GrpcService) DeepCopyInto(out *GrpcService) {
p := proto.Clone(in).(*GrpcService)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrpcService. Required by controller-gen.
func (in *GrpcService) DeepCopy() *GrpcService {
if in == nil {
return nil
}
out := new(GrpcService)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new GrpcService. Required by controller-gen.
func (in *GrpcService) DeepCopyInterface() interface{} {
return in.DeepCopy()
}

View File

@@ -0,0 +1,78 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: networking/v1/http_2_rpc.proto
package v1
import (
bytes "bytes"
fmt "fmt"
github_com_gogo_protobuf_jsonpb "github.com/gogo/protobuf/jsonpb"
proto "github.com/gogo/protobuf/proto"
_ "istio.io/gogo-genproto/googleapis/google/api"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// MarshalJSON is a custom marshaler for Http2Rpc
func (this *Http2Rpc) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for Http2Rpc
func (this *Http2Rpc) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for DubboService
func (this *DubboService) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for DubboService
func (this *DubboService) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for Method
func (this *Method) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for Method
func (this *Method) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for Param
func (this *Param) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for Param
func (this *Param) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for GrpcService
func (this *GrpcService) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for GrpcService
func (this *GrpcService) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
var (
Http_2RpcMarshaler = &github_com_gogo_protobuf_jsonpb.Marshaler{}
Http_2RpcUnmarshaler = &github_com_gogo_protobuf_jsonpb.Unmarshaler{AllowUnknownFields: true}
)

View File

@@ -88,22 +88,26 @@ func (m *McpBridge) GetRegistries() []*RegistryConfig {
}
type RegistryConfig struct {
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"`
NacosAddressServer string `protobuf:"bytes,5,opt,name=nacosAddressServer,proto3" json:"nacosAddressServer,omitempty"`
NacosAccessKey string `protobuf:"bytes,6,opt,name=nacosAccessKey,proto3" json:"nacosAccessKey,omitempty"`
NacosSecretKey string `protobuf:"bytes,7,opt,name=nacosSecretKey,proto3" json:"nacosSecretKey,omitempty"`
NacosNamespaceId string `protobuf:"bytes,8,opt,name=nacosNamespaceId,proto3" json:"nacosNamespaceId,omitempty"`
NacosNamespace string `protobuf:"bytes,9,opt,name=nacosNamespace,proto3" json:"nacosNamespace,omitempty"`
NacosGroups []string `protobuf:"bytes,10,rep,name=nacosGroups,proto3" json:"nacosGroups,omitempty"`
NacosRefreshInterval int64 `protobuf:"varint,11,opt,name=nacosRefreshInterval,proto3" json:"nacosRefreshInterval,omitempty"`
ConsulNamespace string `protobuf:"bytes,12,opt,name=consulNamespace,proto3" json:"consulNamespace,omitempty"`
ZkServicesPath []string `protobuf:"bytes,13,rep,name=zkServicesPath,proto3" json:"zkServicesPath,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Domain string `protobuf:"bytes,3,opt,name=domain,proto3" json:"domain,omitempty"`
Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"`
NacosAddressServer string `protobuf:"bytes,5,opt,name=nacosAddressServer,proto3" json:"nacosAddressServer,omitempty"`
NacosAccessKey string `protobuf:"bytes,6,opt,name=nacosAccessKey,proto3" json:"nacosAccessKey,omitempty"`
NacosSecretKey string `protobuf:"bytes,7,opt,name=nacosSecretKey,proto3" json:"nacosSecretKey,omitempty"`
NacosNamespaceId string `protobuf:"bytes,8,opt,name=nacosNamespaceId,proto3" json:"nacosNamespaceId,omitempty"`
NacosNamespace string `protobuf:"bytes,9,opt,name=nacosNamespace,proto3" json:"nacosNamespace,omitempty"`
NacosGroups []string `protobuf:"bytes,10,rep,name=nacosGroups,proto3" json:"nacosGroups,omitempty"`
NacosRefreshInterval int64 `protobuf:"varint,11,opt,name=nacosRefreshInterval,proto3" json:"nacosRefreshInterval,omitempty"`
ConsulNamespace string `protobuf:"bytes,12,opt,name=consulNamespace,proto3" json:"consulNamespace,omitempty"`
ZkServicesPath []string `protobuf:"bytes,13,rep,name=zkServicesPath,proto3" json:"zkServicesPath,omitempty"`
ConsulDatacenter string `protobuf:"bytes,14,opt,name=consulDatacenter,proto3" json:"consulDatacenter,omitempty"`
ConsulServiceTag string `protobuf:"bytes,15,opt,name=consulServiceTag,proto3" json:"consulServiceTag,omitempty"`
ConsulRefreshInterval int64 `protobuf:"varint,16,opt,name=consulRefreshInterval,proto3" json:"consulRefreshInterval,omitempty"`
AuthSecretName string `protobuf:"bytes,17,opt,name=authSecretName,proto3" json:"authSecretName,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RegistryConfig) Reset() { *m = RegistryConfig{} }
@@ -230,6 +234,34 @@ func (m *RegistryConfig) GetZkServicesPath() []string {
return nil
}
func (m *RegistryConfig) GetConsulDatacenter() string {
if m != nil {
return m.ConsulDatacenter
}
return ""
}
func (m *RegistryConfig) GetConsulServiceTag() string {
if m != nil {
return m.ConsulServiceTag
}
return ""
}
func (m *RegistryConfig) GetConsulRefreshInterval() int64 {
if m != nil {
return m.ConsulRefreshInterval
}
return 0
}
func (m *RegistryConfig) GetAuthSecretName() string {
if m != nil {
return m.AuthSecretName
}
return ""
}
func init() {
proto.RegisterType((*McpBridge)(nil), "higress.networking.v1.McpBridge")
proto.RegisterType((*RegistryConfig)(nil), "higress.networking.v1.RegistryConfig")
@@ -238,34 +270,37 @@ func init() {
func init() { proto.RegisterFile("networking/v1/mcp_bridge.proto", fileDescriptor_3fcc59a15c34642d) }
var fileDescriptor_3fcc59a15c34642d = []byte{
// 421 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xd1, 0x8a, 0xd4, 0x30,
0x14, 0x86, 0xc9, 0x76, 0x1c, 0x9d, 0x8c, 0xbb, 0x4a, 0x50, 0x08, 0x22, 0x63, 0x59, 0x50, 0x8a,
0x48, 0xcb, 0xae, 0x77, 0xde, 0xed, 0x88, 0xc8, 0x22, 0x8a, 0x74, 0xef, 0xbc, 0x59, 0xd2, 0xf4,
0x4c, 0x1a, 0xb6, 0x4d, 0x42, 0x92, 0xa9, 0x8c, 0x4f, 0xe8, 0xa5, 0x8f, 0x20, 0x7d, 0x04, 0x9f,
0x40, 0x9a, 0x59, 0xbb, 0x9d, 0x71, 0xee, 0xda, 0xef, 0x7c, 0xf9, 0x73, 0x08, 0x3f, 0x5e, 0x28,
0xf0, 0xdf, 0xb5, 0xbd, 0x91, 0x4a, 0x64, 0xed, 0x59, 0xd6, 0x70, 0x73, 0x5d, 0x58, 0x59, 0x0a,
0x48, 0x8d, 0xd5, 0x5e, 0x93, 0xa7, 0x95, 0x14, 0x16, 0x9c, 0x4b, 0xef, 0xbc, 0xb4, 0x3d, 0x7b,
0xf6, 0x42, 0x68, 0x2d, 0x6a, 0xc8, 0x98, 0x91, 0xd9, 0x4a, 0x42, 0x5d, 0x5e, 0x17, 0x50, 0xb1,
0x56, 0x6a, 0xbb, 0x3d, 0x77, 0x9a, 0xe3, 0xd9, 0x67, 0x6e, 0x96, 0x21, 0x8a, 0x7c, 0xc0, 0xd8,
0x82, 0x90, 0xce, 0x5b, 0x09, 0x8e, 0xa2, 0x38, 0x4a, 0xe6, 0xe7, 0x2f, 0xd3, 0x83, 0xc9, 0x69,
0xbe, 0x15, 0x37, 0xef, 0xb5, 0x5a, 0x49, 0x91, 0x8f, 0x0e, 0x9e, 0xfe, 0x89, 0xf0, 0xc9, 0xee,
0x98, 0x50, 0x3c, 0xf1, 0x1b, 0x03, 0x14, 0xc5, 0x28, 0x99, 0x2d, 0x27, 0xdd, 0x05, 0x3a, 0xca,
0x03, 0x21, 0x04, 0x4f, 0x14, 0x6b, 0x80, 0x1e, 0xf5, 0x93, 0x3c, 0x7c, 0x93, 0xe7, 0x78, 0x5a,
0xea, 0x86, 0x49, 0x45, 0xa3, 0x91, 0x7f, 0xcb, 0xfa, 0x2c, 0xa3, 0xad, 0xa7, 0x93, 0x18, 0x25,
0xc7, 0xff, 0xb2, 0x7a, 0x42, 0x52, 0x4c, 0x14, 0xe3, 0xda, 0x5d, 0x94, 0x65, 0xbf, 0xf1, 0x15,
0xd8, 0x16, 0x2c, 0xbd, 0x17, 0x92, 0x0f, 0x4c, 0xc8, 0x2b, 0x7c, 0xb2, 0xa5, 0x9c, 0x83, 0x73,
0x9f, 0x60, 0x43, 0xa7, 0xc1, 0xdd, 0xa3, 0x83, 0x77, 0x05, 0xdc, 0x82, 0xef, 0xbd, 0xfb, 0x23,
0x6f, 0xa0, 0xe4, 0x35, 0x7e, 0x1c, 0xc8, 0x17, 0xd6, 0x80, 0x33, 0x8c, 0xc3, 0x65, 0x49, 0x1f,
0x04, 0xf3, 0x3f, 0x3e, 0x64, 0x0e, 0x8c, 0xce, 0x46, 0x99, 0x03, 0x25, 0x31, 0x9e, 0x07, 0xf2,
0xd1, 0xea, 0xb5, 0x71, 0x14, 0xc7, 0x51, 0x32, 0xcb, 0xc7, 0x88, 0x9c, 0xe3, 0x27, 0xe1, 0x37,
0x87, 0x95, 0x05, 0x57, 0x5d, 0x2a, 0x0f, 0xb6, 0x65, 0x35, 0x9d, 0xc7, 0x28, 0x89, 0xf2, 0x83,
0x33, 0x92, 0xe0, 0x47, 0x5c, 0x2b, 0xb7, 0xae, 0xef, 0xae, 0x7f, 0x18, 0xae, 0xdf, 0xc7, 0xfd,
0x9e, 0x3f, 0x6e, 0xfa, 0xf7, 0x92, 0x1c, 0xdc, 0x57, 0xe6, 0x2b, 0x7a, 0x1c, 0x56, 0xd8, 0xa3,
0xcb, 0x77, 0x3f, 0xbb, 0x05, 0xfa, 0xd5, 0x2d, 0xd0, 0xef, 0x6e, 0x81, 0xbe, 0xbd, 0x11, 0xd2,
0x57, 0xeb, 0x22, 0xe5, 0xba, 0xc9, 0x58, 0x2d, 0x0b, 0x56, 0xb0, 0xec, 0xb6, 0x47, 0xa1, 0x8b,
0x3b, 0x6d, 0x2e, 0xa6, 0xa1, 0x8b, 0x6f, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xb9, 0x6f, 0xf7,
0xf0, 0xe5, 0x02, 0x00, 0x00,
// 477 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x93, 0xd1, 0x6e, 0xd3, 0x30,
0x14, 0x86, 0x95, 0xb5, 0x2b, 0xd4, 0x65, 0xdd, 0xb0, 0x98, 0x64, 0x21, 0x54, 0xa2, 0x49, 0xa0,
0x08, 0xa1, 0x44, 0x1b, 0x5c, 0x71, 0xb7, 0x02, 0x42, 0x13, 0x02, 0xa1, 0x8c, 0x2b, 0x6e, 0x26,
0xc7, 0x39, 0x75, 0xac, 0x26, 0x71, 0x64, 0xbb, 0x41, 0xe5, 0x89, 0x78, 0x14, 0x2e, 0x79, 0x04,
0xd4, 0x27, 0x41, 0x76, 0x4a, 0x9a, 0x86, 0xde, 0x25, 0xdf, 0xf9, 0xfd, 0x9f, 0xf3, 0x3b, 0x39,
0x68, 0x56, 0x82, 0xf9, 0x2e, 0xd5, 0x52, 0x94, 0x3c, 0xaa, 0x2f, 0xa3, 0x82, 0x55, 0x77, 0x89,
0x12, 0x29, 0x87, 0xb0, 0x52, 0xd2, 0x48, 0x7c, 0x9e, 0x09, 0xae, 0x40, 0xeb, 0x70, 0xa7, 0x0b,
0xeb, 0xcb, 0xc7, 0x4f, 0xb9, 0x94, 0x3c, 0x87, 0x88, 0x56, 0x22, 0x5a, 0x08, 0xc8, 0xd3, 0xbb,
0x04, 0x32, 0x5a, 0x0b, 0xa9, 0x9a, 0x73, 0x17, 0x31, 0x1a, 0x7f, 0x62, 0xd5, 0xdc, 0x59, 0xe1,
0xf7, 0x08, 0x29, 0xe0, 0x42, 0x1b, 0x25, 0x40, 0x13, 0xcf, 0x1f, 0x04, 0x93, 0xab, 0x67, 0xe1,
0x41, 0xe7, 0x30, 0x6e, 0x84, 0xeb, 0xb7, 0xb2, 0x5c, 0x08, 0x1e, 0x77, 0x0e, 0x5e, 0xfc, 0x3c,
0x46, 0xd3, 0xfd, 0x32, 0x26, 0x68, 0x68, 0xd6, 0x15, 0x10, 0xcf, 0xf7, 0x82, 0xf1, 0x7c, 0xb8,
0xb9, 0xf6, 0x8e, 0x62, 0x47, 0x30, 0x46, 0xc3, 0x92, 0x16, 0x40, 0x8e, 0x6c, 0x25, 0x76, 0xcf,
0xf8, 0x09, 0x1a, 0xa5, 0xb2, 0xa0, 0xa2, 0x24, 0x83, 0x8e, 0x7e, 0xcb, 0xac, 0x57, 0x25, 0x95,
0x21, 0x43, 0xdf, 0x0b, 0x4e, 0xfe, 0x79, 0x59, 0x82, 0x43, 0x84, 0x4b, 0xca, 0xa4, 0xbe, 0x4e,
0x53, 0x3b, 0xf1, 0x2d, 0xa8, 0x1a, 0x14, 0x39, 0x76, 0xce, 0x07, 0x2a, 0xf8, 0x39, 0x9a, 0x36,
0x94, 0x31, 0xd0, 0xfa, 0x23, 0xac, 0xc9, 0xc8, 0x69, 0x7b, 0xb4, 0xd5, 0xdd, 0x02, 0x53, 0x60,
0xac, 0xee, 0x5e, 0x47, 0xd7, 0x52, 0xfc, 0x02, 0x9d, 0x39, 0xf2, 0x99, 0x16, 0xa0, 0x2b, 0xca,
0xe0, 0x26, 0x25, 0xf7, 0x9d, 0xf2, 0x3f, 0xde, 0x7a, 0xb6, 0x8c, 0x8c, 0x3b, 0x9e, 0x2d, 0xc5,
0x3e, 0x9a, 0x38, 0xf2, 0x41, 0xc9, 0x55, 0xa5, 0x09, 0xf2, 0x07, 0xc1, 0x38, 0xee, 0x22, 0x7c,
0x85, 0x1e, 0xb9, 0xd7, 0x18, 0x16, 0x0a, 0x74, 0x76, 0x53, 0x1a, 0x50, 0x35, 0xcd, 0xc9, 0xc4,
0xf7, 0x82, 0x41, 0x7c, 0xb0, 0x86, 0x03, 0x74, 0xca, 0x64, 0xa9, 0x57, 0xf9, 0xae, 0xfd, 0x03,
0xd7, 0xbe, 0x8f, 0xed, 0x9c, 0x3f, 0x96, 0xf6, 0xbe, 0x04, 0x03, 0xfd, 0x85, 0x9a, 0x8c, 0x9c,
0xb8, 0x11, 0x7a, 0xd4, 0x66, 0x6f, 0x8e, 0xbe, 0xa3, 0x86, 0x32, 0xb0, 0x8d, 0xc8, 0xb4, 0xc9,
0xde, 0xe7, 0x3b, 0xed, 0xd6, 0xe1, 0x2b, 0xe5, 0xe4, 0xb4, 0xab, 0xdd, 0x71, 0xfc, 0x1a, 0x9d,
0x37, 0xac, 0x1f, 0xef, 0xcc, 0xc5, 0x3b, 0x5c, 0xb4, 0x53, 0xd3, 0x95, 0xc9, 0x9a, 0x4f, 0x63,
0xc3, 0x90, 0x87, 0xcd, 0xed, 0xee, 0xd3, 0xf9, 0x9b, 0x5f, 0x9b, 0x99, 0xf7, 0x7b, 0x33, 0xf3,
0xfe, 0x6c, 0x66, 0xde, 0xb7, 0x97, 0x5c, 0x98, 0x6c, 0x95, 0x84, 0x4c, 0x16, 0x11, 0xcd, 0x45,
0x42, 0x13, 0x1a, 0x6d, 0xff, 0x7e, 0xb7, 0x41, 0x7b, 0x3b, 0x98, 0x8c, 0xdc, 0x06, 0xbd, 0xfa,
0x1b, 0x00, 0x00, 0xff, 0xff, 0x21, 0x2e, 0x82, 0x0a, 0x9b, 0x03, 0x00, 0x00,
}
func (m *McpBridge) Marshal() (dAtA []byte, err error) {
@@ -333,6 +368,36 @@ func (m *RegistryConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.AuthSecretName) > 0 {
i -= len(m.AuthSecretName)
copy(dAtA[i:], m.AuthSecretName)
i = encodeVarintMcpBridge(dAtA, i, uint64(len(m.AuthSecretName)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x8a
}
if m.ConsulRefreshInterval != 0 {
i = encodeVarintMcpBridge(dAtA, i, uint64(m.ConsulRefreshInterval))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x80
}
if len(m.ConsulServiceTag) > 0 {
i -= len(m.ConsulServiceTag)
copy(dAtA[i:], m.ConsulServiceTag)
i = encodeVarintMcpBridge(dAtA, i, uint64(len(m.ConsulServiceTag)))
i--
dAtA[i] = 0x7a
}
if len(m.ConsulDatacenter) > 0 {
i -= len(m.ConsulDatacenter)
copy(dAtA[i:], m.ConsulDatacenter)
i = encodeVarintMcpBridge(dAtA, i, uint64(len(m.ConsulDatacenter)))
i--
dAtA[i] = 0x72
}
if len(m.ZkServicesPath) > 0 {
for iNdEx := len(m.ZkServicesPath) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.ZkServicesPath[iNdEx])
@@ -516,6 +581,21 @@ func (m *RegistryConfig) Size() (n int) {
n += 1 + l + sovMcpBridge(uint64(l))
}
}
l = len(m.ConsulDatacenter)
if l > 0 {
n += 1 + l + sovMcpBridge(uint64(l))
}
l = len(m.ConsulServiceTag)
if l > 0 {
n += 1 + l + sovMcpBridge(uint64(l))
}
if m.ConsulRefreshInterval != 0 {
n += 2 + sovMcpBridge(uint64(m.ConsulRefreshInterval))
}
l = len(m.AuthSecretName)
if l > 0 {
n += 2 + l + sovMcpBridge(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -1032,6 +1112,121 @@ func (m *RegistryConfig) Unmarshal(dAtA []byte) error {
}
m.ZkServicesPath = append(m.ZkServicesPath, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 14:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ConsulDatacenter", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowMcpBridge
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthMcpBridge
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthMcpBridge
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ConsulDatacenter = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 15:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field ConsulServiceTag", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowMcpBridge
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthMcpBridge
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthMcpBridge
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ConsulServiceTag = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 16:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field ConsulRefreshInterval", wireType)
}
m.ConsulRefreshInterval = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowMcpBridge
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.ConsulRefreshInterval |= int64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 17:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field AuthSecretName", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowMcpBridge
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthMcpBridge
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthMcpBridge
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.AuthSecretName = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipMcpBridge(dAtA[iNdEx:])

View File

@@ -60,4 +60,8 @@ message RegistryConfig {
int64 nacosRefreshInterval = 11;
string consulNamespace = 12;
repeated string zkServicesPath = 13;
string consulDatacenter = 14;
string consulServiceTag = 15;
int64 consulRefreshInterval = 16;
string authSecretName = 17;
}

View File

@@ -41,6 +41,8 @@ func Resource(resource string) schema.GroupResource {
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Http2Rpc{},
&Http2RpcList{},
&McpBridge{},
&McpBridgeList{},
)

View File

@@ -25,6 +25,48 @@ import (
// please upgrade the proto package
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// <!-- crd generation tags
// +cue-gen:Http2Rpc:groupName:networking.higress.io
// +cue-gen:Http2Rpc:version:v1
// +cue-gen:Http2Rpc:storageVersion
// +cue-gen:Http2Rpc:annotations:helm.sh/resource-policy=keep
// +cue-gen:Http2Rpc:subresource:status
// +cue-gen:Http2Rpc:scope:Namespaced
// +cue-gen:Http2Rpc:resource:categories=higress-io,plural=http2rpcs
// +cue-gen:Http2Rpc:preserveUnknownFields:false
// -->
//
// <!-- go code generation tags
// +kubetype-gen
// +kubetype-gen:groupVersion=networking.higress.io/v1
// +genclient
// +k8s:deepcopy-gen=true
// -->
type Http2Rpc struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec defines the implementation of this definition.
// +optional
Spec networkingv1.Http2Rpc `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status v1alpha1.IstioStatus `json:"status"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Http2RpcList is a collection of Http2Rpcs.
type Http2RpcList struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Items []Http2Rpc `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// please upgrade the proto package
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// <!-- crd generation tags
// +cue-gen:McpBridge:groupName:networking.higress.io
// +cue-gen:McpBridge:version:v1

View File

@@ -23,6 +23,67 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Http2Rpc) DeepCopyInto(out *Http2Rpc) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Http2Rpc.
func (in *Http2Rpc) DeepCopy() *Http2Rpc {
if in == nil {
return nil
}
out := new(Http2Rpc)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Http2Rpc) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Http2RpcList) DeepCopyInto(out *Http2RpcList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Http2Rpc, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Http2RpcList.
func (in *Http2RpcList) DeepCopy() *Http2RpcList {
if in == nil {
return nil
}
out := new(Http2RpcList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Http2RpcList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *McpBridge) DeepCopyInto(out *McpBridge) {
*out = *in

View File

@@ -0,0 +1,140 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
networkingv1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeHttp2Rpcs implements Http2RpcInterface
type FakeHttp2Rpcs struct {
Fake *FakeNetworkingV1
ns string
}
var http2rpcsResource = schema.GroupVersionResource{Group: "networking.higress.io", Version: "v1", Resource: "http2rpcs"}
var http2rpcsKind = schema.GroupVersionKind{Group: "networking.higress.io", Version: "v1", Kind: "Http2Rpc"}
// Get takes name of the http2Rpc, and returns the corresponding http2Rpc object, and an error if there is any.
func (c *FakeHttp2Rpcs) Get(ctx context.Context, name string, options v1.GetOptions) (result *networkingv1.Http2Rpc, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(http2rpcsResource, c.ns, name), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}
// List takes label and field selectors, and returns the list of Http2Rpcs that match those selectors.
func (c *FakeHttp2Rpcs) List(ctx context.Context, opts v1.ListOptions) (result *networkingv1.Http2RpcList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(http2rpcsResource, http2rpcsKind, c.ns, opts), &networkingv1.Http2RpcList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &networkingv1.Http2RpcList{ListMeta: obj.(*networkingv1.Http2RpcList).ListMeta}
for _, item := range obj.(*networkingv1.Http2RpcList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested http2Rpcs.
func (c *FakeHttp2Rpcs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(http2rpcsResource, c.ns, opts))
}
// Create takes the representation of a http2Rpc and creates it. Returns the server's representation of the http2Rpc, and an error, if there is any.
func (c *FakeHttp2Rpcs) Create(ctx context.Context, http2Rpc *networkingv1.Http2Rpc, opts v1.CreateOptions) (result *networkingv1.Http2Rpc, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(http2rpcsResource, c.ns, http2Rpc), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}
// Update takes the representation of a http2Rpc and updates it. Returns the server's representation of the http2Rpc, and an error, if there is any.
func (c *FakeHttp2Rpcs) Update(ctx context.Context, http2Rpc *networkingv1.Http2Rpc, opts v1.UpdateOptions) (result *networkingv1.Http2Rpc, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(http2rpcsResource, c.ns, http2Rpc), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeHttp2Rpcs) UpdateStatus(ctx context.Context, http2Rpc *networkingv1.Http2Rpc, opts v1.UpdateOptions) (*networkingv1.Http2Rpc, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(http2rpcsResource, "status", c.ns, http2Rpc), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}
// Delete takes name of the http2Rpc and deletes it. Returns an error if one occurs.
func (c *FakeHttp2Rpcs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(http2rpcsResource, c.ns, name), &networkingv1.Http2Rpc{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeHttp2Rpcs) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(http2rpcsResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &networkingv1.Http2RpcList{})
return err
}
// Patch applies the patch and returns the patched http2Rpc.
func (c *FakeHttp2Rpcs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *networkingv1.Http2Rpc, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(http2rpcsResource, c.ns, name, pt, data, subresources...), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}

View File

@@ -26,6 +26,10 @@ type FakeNetworkingV1 struct {
*testing.Fake
}
func (c *FakeNetworkingV1) Http2Rpcs(namespace string) v1.Http2RpcInterface {
return &FakeHttp2Rpcs{c, namespace}
}
func (c *FakeNetworkingV1) McpBridges(namespace string) v1.McpBridgeInterface {
return &FakeMcpBridges{c, namespace}
}

View File

@@ -16,4 +16,6 @@
package v1
type Http2RpcExpansion interface{}
type McpBridgeExpansion interface{}

View File

@@ -0,0 +1,193 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by client-gen. DO NOT EDIT.
package v1
import (
"context"
"time"
v1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
scheme "github.com/alibaba/higress/client/pkg/clientset/versioned/scheme"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// Http2RpcsGetter has a method to return a Http2RpcInterface.
// A group's client should implement this interface.
type Http2RpcsGetter interface {
Http2Rpcs(namespace string) Http2RpcInterface
}
// Http2RpcInterface has methods to work with Http2Rpc resources.
type Http2RpcInterface interface {
Create(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.CreateOptions) (*v1.Http2Rpc, error)
Update(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.UpdateOptions) (*v1.Http2Rpc, error)
UpdateStatus(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.UpdateOptions) (*v1.Http2Rpc, error)
Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Http2Rpc, error)
List(ctx context.Context, opts metav1.ListOptions) (*v1.Http2RpcList, error)
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Http2Rpc, err error)
Http2RpcExpansion
}
// http2Rpcs implements Http2RpcInterface
type http2Rpcs struct {
client rest.Interface
ns string
}
// newHttp2Rpcs returns a Http2Rpcs
func newHttp2Rpcs(c *NetworkingV1Client, namespace string) *http2Rpcs {
return &http2Rpcs{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the http2Rpc, and returns the corresponding http2Rpc object, and an error if there is any.
func (c *http2Rpcs) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Get().
Namespace(c.ns).
Resource("http2rpcs").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of Http2Rpcs that match those selectors.
func (c *http2Rpcs) List(ctx context.Context, opts metav1.ListOptions) (result *v1.Http2RpcList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.Http2RpcList{}
err = c.client.Get().
Namespace(c.ns).
Resource("http2rpcs").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested http2Rpcs.
func (c *http2Rpcs) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("http2rpcs").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a http2Rpc and creates it. Returns the server's representation of the http2Rpc, and an error, if there is any.
func (c *http2Rpcs) Create(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.CreateOptions) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Post().
Namespace(c.ns).
Resource("http2rpcs").
VersionedParams(&opts, scheme.ParameterCodec).
Body(http2Rpc).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a http2Rpc and updates it. Returns the server's representation of the http2Rpc, and an error, if there is any.
func (c *http2Rpcs) Update(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.UpdateOptions) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Put().
Namespace(c.ns).
Resource("http2rpcs").
Name(http2Rpc.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(http2Rpc).
Do(ctx).
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *http2Rpcs) UpdateStatus(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.UpdateOptions) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Put().
Namespace(c.ns).
Resource("http2rpcs").
Name(http2Rpc.Name).
SubResource("status").
VersionedParams(&opts, scheme.ParameterCodec).
Body(http2Rpc).
Do(ctx).
Into(result)
return
}
// Delete takes name of the http2Rpc and deletes it. Returns an error if one occurs.
func (c *http2Rpcs) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("http2rpcs").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *http2Rpcs) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("http2rpcs").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched http2Rpc.
func (c *http2Rpcs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("http2rpcs").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}

View File

@@ -24,6 +24,7 @@ import (
type NetworkingV1Interface interface {
RESTClient() rest.Interface
Http2RpcsGetter
McpBridgesGetter
}
@@ -32,6 +33,10 @@ type NetworkingV1Client struct {
restClient rest.Interface
}
func (c *NetworkingV1Client) Http2Rpcs(namespace string) Http2RpcInterface {
return newHttp2Rpcs(c, namespace)
}
func (c *NetworkingV1Client) McpBridges(namespace string) McpBridgeInterface {
return newMcpBridges(c, namespace)
}

View File

@@ -56,6 +56,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Extensions().V1alpha1().WasmPlugins().Informer()}, nil
// Group=networking.higress.io, Version=v1
case v1.SchemeGroupVersion.WithResource("http2rpcs"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1().Http2Rpcs().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("mcpbridges"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1().McpBridges().Informer()}, nil

View File

@@ -0,0 +1,88 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by informer-gen. DO NOT EDIT.
package v1
import (
"context"
time "time"
networkingv1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
versioned "github.com/alibaba/higress/client/pkg/clientset/versioned"
internalinterfaces "github.com/alibaba/higress/client/pkg/informers/externalversions/internalinterfaces"
v1 "github.com/alibaba/higress/client/pkg/listers/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// Http2RpcInformer provides access to a shared informer and lister for
// Http2Rpcs.
type Http2RpcInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.Http2RpcLister
}
type http2RpcInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewHttp2RpcInformer constructs a new informer for Http2Rpc type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewHttp2RpcInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredHttp2RpcInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredHttp2RpcInformer constructs a new informer for Http2Rpc type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredHttp2RpcInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.NetworkingV1().Http2Rpcs(namespace).List(context.TODO(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.NetworkingV1().Http2Rpcs(namespace).Watch(context.TODO(), options)
},
},
&networkingv1.Http2Rpc{},
resyncPeriod,
indexers,
)
}
func (f *http2RpcInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredHttp2RpcInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *http2RpcInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&networkingv1.Http2Rpc{}, f.defaultInformer)
}
func (f *http2RpcInformer) Lister() v1.Http2RpcLister {
return v1.NewHttp2RpcLister(f.Informer().GetIndexer())
}

View File

@@ -22,6 +22,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// Http2Rpcs returns a Http2RpcInformer.
Http2Rpcs() Http2RpcInformer
// McpBridges returns a McpBridgeInformer.
McpBridges() McpBridgeInformer
}
@@ -37,6 +39,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// Http2Rpcs returns a Http2RpcInformer.
func (v *version) Http2Rpcs() Http2RpcInformer {
return &http2RpcInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// McpBridges returns a McpBridgeInformer.
func (v *version) McpBridges() McpBridgeInformer {
return &mcpBridgeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@@ -16,6 +16,14 @@
package v1
// Http2RpcListerExpansion allows custom methods to be added to
// Http2RpcLister.
type Http2RpcListerExpansion interface{}
// Http2RpcNamespaceListerExpansion allows custom methods to be added to
// Http2RpcNamespaceLister.
type Http2RpcNamespaceListerExpansion interface{}
// McpBridgeListerExpansion allows custom methods to be added to
// McpBridgeLister.
type McpBridgeListerExpansion interface{}

View File

@@ -0,0 +1,92 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by lister-gen. DO NOT EDIT.
package v1
import (
v1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// Http2RpcLister helps list Http2Rpcs.
type Http2RpcLister interface {
// List lists all Http2Rpcs in the indexer.
List(selector labels.Selector) (ret []*v1.Http2Rpc, err error)
// Http2Rpcs returns an object that can list and get Http2Rpcs.
Http2Rpcs(namespace string) Http2RpcNamespaceLister
Http2RpcListerExpansion
}
// http2RpcLister implements the Http2RpcLister interface.
type http2RpcLister struct {
indexer cache.Indexer
}
// NewHttp2RpcLister returns a new Http2RpcLister.
func NewHttp2RpcLister(indexer cache.Indexer) Http2RpcLister {
return &http2RpcLister{indexer: indexer}
}
// List lists all Http2Rpcs in the indexer.
func (s *http2RpcLister) List(selector labels.Selector) (ret []*v1.Http2Rpc, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1.Http2Rpc))
})
return ret, err
}
// Http2Rpcs returns an object that can list and get Http2Rpcs.
func (s *http2RpcLister) Http2Rpcs(namespace string) Http2RpcNamespaceLister {
return http2RpcNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// Http2RpcNamespaceLister helps list and get Http2Rpcs.
type Http2RpcNamespaceLister interface {
// List lists all Http2Rpcs in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1.Http2Rpc, err error)
// Get retrieves the Http2Rpc from the indexer for a given namespace and name.
Get(name string) (*v1.Http2Rpc, error)
Http2RpcNamespaceListerExpansion
}
// http2RpcNamespaceLister implements the Http2RpcNamespaceLister
// interface.
type http2RpcNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all Http2Rpcs in the indexer for a given namespace.
func (s http2RpcNamespaceLister) List(selector labels.Selector) (ret []*v1.Http2Rpc, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1.Http2Rpc))
})
return ret, err
}
// Get retrieves the Http2Rpc from the indexer for a given namespace and name.
func (s http2RpcNamespaceLister) Get(name string) (*v1.Http2Rpc, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1.Resource("http2rpc"), name)
}
return obj.(*v1.Http2Rpc), nil
}

33
docker/Dockerfile.base Normal file
View File

@@ -0,0 +1,33 @@
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
# Do not add more stuff to this list that isn't small or critically useful.
# If you occasionally need something on the container do
# sudo apt-get update && apt-get whichever
# hadolint ignore=DL3005,DL3008
RUN apt-get update && \
apt-get install --no-install-recommends -y \
ca-certificates \
curl \
iptables \
iproute2 \
iputils-ping \
knot-dnsutils \
netcat \
tcpdump \
conntrack \
bsdmainutils \
net-tools \
lsof \
sudo \
&& apt-get upgrade -y \
&& apt-get clean \
&& rm -rf /var/log/*log /var/lib/apt/lists/* /var/log/apt/* /var/lib/dpkg/*-old /var/cache/debconf/*-old \
&& update-alternatives --set iptables /usr/sbin/iptables-legacy \
&& update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
# Sudoers used to allow tcpdump and other debug utilities.
RUN useradd -m --uid 1337 istio-proxy && \
echo "istio-proxy ALL=NOPASSWD: ALL" >> /etc/sudoers

View File

@@ -11,6 +11,7 @@ ARG HUB
FROM ${HUB}/base:${BASE_VERSION}
ARG TARGETARCH
COPY ${TARGETARCH:-amd64}/higress /usr/local/bin/higress
USER 1337:1337

View File

@@ -17,6 +17,12 @@ docker.higress: $(OUT_LINUX)/higress
docker.higress: docker/Dockerfile.higress
$(HIGRESS_DOCKER_RULE)
docker.higress-buildx: BUILD_ARGS=--build-arg BASE_VERSION=${BASE_VERSION} --build-arg HUB=${HUB}
docker.higress-buildx: $(AMD64_OUT_LINUX)/higress
docker.higress-buildx: $(ARM64_OUT_LINUX)/higress
docker.higress-buildx: docker/Dockerfile.higress
$(HIGRESS_DOCKER_BUILDX_RULE)
# DOCKER_BUILD_VARIANTS ?=debug distroless
# Base images have two different forms:
# * "debug", suffixed as -debug. This is a ubuntu based image with a bunch of debug tools
@@ -28,4 +34,7 @@ DOCKER_ALL_VARIANTS ?= debug distroless
# This can be done with DOCKER_BUILD_VARIANTS="default debug" as well, but at the expense of building twice vs building once and tagging twice
INCLUDE_UNTAGGED_DEFAULT ?= false
DEFAULT_DISTRIBUTION=debug
HIGRESS_DOCKER_RULE ?= $(foreach VARIANT,$(DOCKER_BUILD_VARIANTS), time (mkdir -p $(HIGRESS_DOCKER_BUILD_TOP)/$@ && TARGET_ARCH=$(TARGET_ARCH) ./docker/docker-copy.sh $^ $(HIGRESS_DOCKER_BUILD_TOP)/$@ && cd $(HIGRESS_DOCKER_BUILD_TOP)/$@ $(BUILD_PRE) && docker build $(BUILD_ARGS) --build-arg BASE_DISTRIBUTION=$(call normalize-tag,$(VARIANT)) -t $(HUB)/$(subst docker.,,$@):$(TAG)$(call variant-tag,$(VARIANT)) -f Dockerfile$(suffix $@) . ); )
HIGRESS_DOCKER_BUILDX_RULE ?= $(foreach VARIANT,$(DOCKER_BUILD_VARIANTS), time (mkdir -p $(HIGRESS_DOCKER_BUILD_TOP)/$@ && TARGET_ARCH=$(TARGET_ARCH) ./docker/docker-copy.sh $^ $(HIGRESS_DOCKER_BUILD_TOP)/$@ && cd $(HIGRESS_DOCKER_BUILD_TOP)/$@ $(BUILD_PRE) && docker buildx create --name higress --node higress0 --platform linux/amd64,linux/arm64 --use && docker buildx build --no-cache --platform linux/amd64,linux/arm64 $(BUILD_ARGS) --build-arg BASE_DISTRIBUTION=$(call normalize-tag,$(VARIANT)) -t $(HUB)/higress:$(TAG)$(call variant-tag,$(VARIANT)) -f Dockerfile.higress . --push ); )
HIGRESS_DOCKER_RULE ?= $(foreach VARIANT,$(DOCKER_BUILD_VARIANTS), time (mkdir -p $(HIGRESS_DOCKER_BUILD_TOP)/$@ && TARGET_ARCH=$(TARGET_ARCH) ./docker/docker-copy.sh $^ $(HIGRESS_DOCKER_BUILD_TOP)/$@ && cd $(HIGRESS_DOCKER_BUILD_TOP)/$@ $(BUILD_PRE) && docker build $(BUILD_ARGS) --build-arg BASE_DISTRIBUTION=$(call normalize-tag,$(VARIANT)) -t $(HUB)/higress:$(TAG)$(call variant-tag,$(VARIANT)) -f Dockerfile.higress . ); )

124
go.mod
View File

@@ -17,20 +17,23 @@ replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.5
require (
github.com/agiledragon/gomonkey/v2 v2.9.0
github.com/avast/retry-go/v4 v4.3.4
github.com/dubbogo/go-zookeeper v1.0.4-0.20211212162352-f9d2183d89d5
github.com/dubbogo/gost v1.13.1
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.5.2
github.com/google/go-cmp v0.5.8
github.com/google/go-cmp v0.5.9
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/hashicorp/consul/api v1.23.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hudl/fargo v1.4.0
github.com/nacos-group/nacos-sdk-go v1.0.8
github.com/nacos-group/nacos-sdk-go/v2 v2.1.2
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.3
go.uber.org/atomic v1.9.0
google.golang.org/grpc v1.48.0
google.golang.org/protobuf v1.28.0
@@ -40,17 +43,17 @@ require (
istio.io/gogo-genproto v0.0.0-20211115195057-0e34bdd2be67
istio.io/istio v0.0.0
istio.io/pkg v0.0.0-20211115195056-e379f31ee62a
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/api v0.22.5
k8s.io/apimachinery v0.22.5
k8s.io/cli-runtime v0.22.2
k8s.io/client-go v0.22.2
k8s.io/client-go v0.22.5
k8s.io/kubectl v0.22.2
sigs.k8s.io/controller-runtime v0.10.2
sigs.k8s.io/yaml v1.3.0
)
require (
cloud.google.com/go v0.97.0 // indirect
cloud.google.com/go v0.98.0 // indirect
cloud.google.com/go/logging v1.4.2 // indirect
contrib.go.opencensus.io/exporter/prometheus v0.4.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
@@ -60,46 +63,61 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/Masterminds/squirrel v1.5.0 // indirect
github.com/Microsoft/go-winio v0.5.0 // indirect
github.com/Microsoft/hcsshim v0.8.21 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/RageCage64/multilinediff v0.2.0 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/aws/aws-sdk-go v1.41.7 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
github.com/braydonk/yaml v0.7.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
github.com/clbanning/mxj v1.8.4 // indirect
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
github.com/cncf/xds/go v0.0.0-20220520190051-1e77728a1eaa // indirect
github.com/containerd/containerd v1.5.7 // indirect
github.com/containerd/continuity v0.1.0 // indirect
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 // indirect
github.com/docker/cli v20.10.7+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.7+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.3 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.14.1 // indirect
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-kit/log v0.1.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-logr/logr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.4.8 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -111,18 +129,29 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-version v1.3.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmoiron/sqlx v1.3.1 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.0 // indirect
@@ -131,35 +160,50 @@ require (
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f // indirect
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
github.com/lib/pq v1.10.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/miekg/dns v1.1.43 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v1.0.2 // indirect
github.com/openshift/api v0.0.0-20200713203337-b2494ecb17dd // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/statsd_exporter v0.21.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect
github.com/russross/blackfriday v1.5.2 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect
go.opencensus.io v0.23.0 // indirect
@@ -167,30 +211,35 @@ require (
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
gomodules.xyz/jsonpatch/v3 v3.0.1 // indirect
gomodules.xyz/orderedmap v0.1.0 // indirect
google.golang.org/api v0.59.0 // indirect
google.golang.org/api v0.61.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a // indirect
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12 // indirect
gopkg.in/gcfg.v1 v1.2.3 // indirect
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.22.2 // indirect
k8s.io/apiserver v0.22.2 // indirect
k8s.io/component-base v0.22.2 // indirect
k8s.io/klog/v2 v2.10.0 // indirect
k8s.io/kube-openapi v0.0.0-20211020163157-7327e2aaee2b // indirect
k8s.io/klog/v2 v2.40.1 // indirect
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
oras.land/oras-go v0.4.0 // indirect
sigs.k8s.io/gateway-api v0.4.0 // indirect
sigs.k8s.io/kustomize/api v0.8.11 // indirect
sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect
@@ -209,3 +258,30 @@ replace istio.io/pkg => ./external/pkg
replace istio.io/client-go => ./external/client-go
replace istio.io/istio => ./external/istio
replace (
github.com/go-logr/logr => github.com/go-logr/logr v0.4.0
k8s.io/api => k8s.io/api v0.22.2
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.22.2
k8s.io/apimachinery => k8s.io/apimachinery v0.22.2
k8s.io/cli-runtime => k8s.io/cli-runtime v0.22.2
k8s.io/client-go => k8s.io/client-go v0.22.2
k8s.io/component-base => k8s.io/component-base v0.22.2
k8s.io/klog/v2 => k8s.io/klog/v2 v2.10.0
k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20211020163157-7327e2aaee2b // indirect
k8s.io/kubectl => k8s.io/kubectl v0.22.2
k8s.io/utils => k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/gateway-api => sigs.k8s.io/gateway-api v0.4.0 // indirect
sigs.k8s.io/kustomize/api => sigs.k8s.io/kustomize/api v0.8.11 // indirect
sigs.k8s.io/kustomize/kyaml => sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect
)
require (
github.com/evanphx/json-patch/v5 v5.6.0
github.com/google/yamlfmt v0.10.0
github.com/kylelemons/godebug v1.1.0
helm.sh/helm/v3 v3.7.1
k8s.io/apiextensions-apiserver v0.25.4
knative.dev/networking v0.0.0-20220302134042-e8b2eb995165
knative.dev/pkg v0.0.0-20220301181942-2fdd5f232e77
)

463
go.sum
View File

File diff suppressed because it is too large Load Diff

1
helm/core/.helmignore Normal file
View File

@@ -0,0 +1 @@
crds/customresourcedefinitions.gen_lt1.16.yaml

View File

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

View File

@@ -104,6 +104,88 @@ spec:
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
"helm.sh/resource-policy": keep
name: http2rpcs.networking.higress.io
spec:
group: networking.higress.io
names:
categories:
- higress-io
kind: Http2Rpc
listKind: Http2RpcList
plural: http2rpcs
singular: http2rpc
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
properties:
spec:
oneOf:
- not:
anyOf:
- required:
- dubbo
- required:
- grpc
- required:
- dubbo
- required:
- grpc
properties:
dubbo:
properties:
group:
type: string
methods:
items:
properties:
headersAttach:
type: string
httpMethods:
items:
type: string
type: array
httpPath:
type: string
params:
items:
properties:
paramKey:
type: string
paramSource:
type: string
paramType:
type: string
type: object
type: array
serviceMethod:
type: string
type: object
type: array
service:
type: string
version:
type: string
type: object
grpc:
type: object
type: object
status:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
@@ -131,8 +213,17 @@ spec:
registries:
items:
properties:
authSecretName:
type: string
consulDatacenter:
type: string
consulNamespace:
type: string
consulRefreshInterval:
format: int64
type: integer
consulServiceTag:
type: string
domain:
type: string
nacosAccessKey:

View File

@@ -0,0 +1,176 @@
# DO NOT EDIT - Generated by Cue OpenAPI generator based on Istio APIs.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
"helm.sh/resource-policy": keep
name: wasmplugins.extensions.higress.io
spec:
group: extensions.higress.io
names:
categories:
- higress-io
- extensions-higress-io
kind: WasmPlugin
listKind: WasmPluginList
plural: wasmplugins
singular: wasmplugin
scope: Namespaced
additionalPrinterColumns:
- description: 'CreationTimestamp is a timestamp representing the server time
when this object was created. It is not guaranteed to be set in happens-before
order across separate operations. Clients may not set this value. It is represented
in RFC3339 form and is in UTC. Populated by the system. Read-only. Null for
lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
JSONPath: .metadata.creationTimestamp
name: Age
type: date
versions:
- name: v1alpha1
served: true
storage: true
version: v1alpha1
validation:
openAPIV3Schema:
properties:
spec:
properties:
defaultConfig:
type: object
x-kubernetes-preserve-unknown-fields: true
defaultConfigDisable:
type: boolean
imagePullPolicy:
description: The pull behaviour to be applied when fetching an OCI
image.
enum:
- UNSPECIFIED_POLICY
- IfNotPresent
- Always
type: string
imagePullSecret:
description: Credentials to use for OCI image pulling.
type: string
matchRules:
items:
properties:
config:
type: object
x-kubernetes-preserve-unknown-fields: true
configDisable:
type: boolean
domain:
items:
type: string
type: array
ingress:
items:
type: string
type: array
type: object
type: array
phase:
description: Determines where in the filter chain this `WasmPlugin`
is to be injected.
enum:
- UNSPECIFIED_PHASE
- AUTHN
- AUTHZ
- STATS
type: string
pluginConfig:
description: The configuration that will be passed on to the plugin.
type: object
x-kubernetes-preserve-unknown-fields: true
pluginName:
type: string
priority:
description: Determines ordering of `WasmPlugins` in the same `phase`.
nullable: true
type: integer
sha256:
description: SHA256 checksum that will be used to verify Wasm module
or OCI container.
type: string
url:
description: URL of a Wasm module or OCI container.
type: string
verificationKey:
type: string
type: object
status:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
"helm.sh/resource-policy": keep
name: mcpbridges.networking.higress.io
spec:
group: networking.higress.io
names:
categories:
- higress-io
kind: McpBridge
listKind: McpBridgeList
plural: mcpbridges
singular: mcpbridge
scope: Namespaced
versions:
- name: v1
served: true
storage: true
version: v1
validation:
openAPIV3Schema:
properties:
spec:
properties:
registries:
items:
properties:
consulNamespace:
type: string
domain:
type: string
nacosAccessKey:
type: string
nacosAddressServer:
type: string
nacosGroups:
items:
type: string
type: array
nacosNamespace:
type: string
nacosNamespaceId:
type: string
nacosRefreshInterval:
format: int64
type: integer
nacosSecretKey:
type: string
name:
type: string
port:
type: integer
type:
type: string
zkServicesPath:
items:
type: string
type: array
type: object
type: array
type: object
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
subresources:
status: {}
---

View File

@@ -95,3 +95,9 @@ higress: {{ include "controller.name" . }}
{{- print "first-party-jwt" }}
{{- end }}
{{- end }}
{{- define "skywalking.enabled" -}}
{{- if and .Values.skywalking.enabled .Values.skywalking.service.address }}
true
{{- end }}
{{- end }}

View File

@@ -24,7 +24,7 @@
configSources:
- address: "xds://127.0.0.1:15051"
{{- if .Values.global.enableIstioAPI }}
{{- if or .Values.global.enableIstioAPI .Values.global.enableGatewayAPI }}
- address: "k8s://"
{{- end }}
@@ -122,7 +122,7 @@ data:
{{- include "mesh" . }}
{{- end }}
---
{{- if .Values.enableSkywalking }}
{{- if include "skywalking.enabled" . }}
apiVersion: v1
kind: ConfigMap
metadata:
@@ -154,7 +154,6 @@ data:
"type": "LOGICAL_DNS",
"connect_timeout": "5s",
"http2_protocol_options": {
},
"dns_lookup_family": "V4_ONLY",
"lb_policy": "ROUND_ROBIN",
@@ -167,8 +166,8 @@ data:
"endpoint": {
"address": {
"socket_address": {
"address": "{{ .Values.Skywalking.address }}",
"port_value": "{{ .Values.Skywalking.port }}"
"address": "{{ .Values.skywalking.service.address }}",
"port_value": "{{ .Values.skywalking.service.port }}"
}
}
}

View File

@@ -46,6 +46,10 @@ rules:
resources: ["wasmplugins"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.higress.io"]
resources: ["http2rpcs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "watch", "list", "update", "patch", "create", "delete"]
@@ -113,3 +117,10 @@ rules:
- apiGroups: ["config.istio.io", "security.istio.io", "networking.istio.io", "authentication.istio.io", "rbac.istio.io", "telemetry.istio.io", "extensions.istio.io"]
verbs: ["get", "watch", "list"]
resources: ["*"]
# knative KIngress configuration
- apiGroups: ["networking.internal.knative.dev"]
verbs: ["get","list","watch"]
resources: ["ingresses"]
- apiGroups: ["networking.internal.knative.dev"]
resources: ["ingresses/status"]
verbs: ["get","patch","update"]

View File

@@ -33,7 +33,7 @@ spec:
{{- if contains "/" .Values.pilot.image }}
image: "{{ .Values.pilot.image }}"
{{- else }}
image: "{{ .Values.pilot.hub | default .Values.global.hub }}/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Values.global.tag }}"
image: "{{ .Values.pilot.hub | default .Values.global.hub }}/{{ .Values.pilot.image | default "pilot" }}:{{ .Values.pilot.tag | default .Chart.AppVersion }}"
{{- end }}
{{- if .Values.global.imagePullPolicy }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
@@ -73,6 +73,8 @@ spec:
periodSeconds: 3
timeoutSeconds: 5
env:
- name: PILOT_FILTER_GATEWAY_CLUSTER_CONFIG
value: "true"
- name: HIGRESS_CONTROLLER_SVC
value: "127.0.0.1"
- name: HIGRESS_CONTROLLER_PORT
@@ -100,6 +102,10 @@ spec:
fieldPath: spec.serviceAccountName
- name: KUBECONFIG
value: /var/run/secrets/remote/config
- name: PRIORITIZED_LEADER_ELECTION
value: "false"
- name: INJECT_ENABLED
value: "false"
{{- if .Values.pilot.env }}
{{- range $key, $val := .Values.pilot.env }}
- name: {{ $key }}
@@ -120,10 +126,19 @@ spec:
value: "{{ .Values.global.istiod.enableAnalysis }}"
- name: CLUSTER_ID
value: "{{ $.Values.global.multiCluster.clusterName | default `Kubernetes` }}"
# HIGRESS_ENABLE_ISTIO_API is only used to restart the controller pod after the config change
{{- if .Values.global.enableIstioAPI }}
- name: HIGRESS_ENABLE_ISTIO_API
value: "true"
{{- end }}
{{- if .Values.global.enableGatewayAPI }}
- name: PILOT_ENABLE_GATEWAY_API
value: "true"
- name: PILOT_ENABLE_GATEWAY_API_STATUS
value: "true"
- name: PILOT_ENABLE_GATEWAY_API_DEPLOYMENT_CONTROLLER
value: "false"
{{- end }}
{{- if not .Values.global.enableHigressIstio }}
- name: CUSTOM_CA_CERT_NAME
value: "higress-ca-root-cert"
@@ -196,6 +211,8 @@ spec:
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 }}

View File

@@ -146,7 +146,7 @@ spec:
value: "{{ $.Values.clusterName | default `Kubernetes` }}"
- name: INSTANCE_NAME
value: "higress-gateway"
{{- if .Values.enableSkywalking }}
{{- if include "skywalking.enabled" . }}
- name: ISTIO_BOOTSTRAP_OVERRIDE
value: /etc/istio/custom-bootstrap/custom_bootstrap.json
{{- end }}
@@ -202,10 +202,14 @@ spec:
mountPath: /etc/istio/pod
- name: proxy-socket
mountPath: /etc/istio/proxy
{{- if .Values.enableSkywalking }}
{{- 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 .Values.gateway.hostNetwork }}
hostNetwork: {{ .Values.gateway.hostNetwork }}
dnsPolicy: ClusterFirstWithHostNet
@@ -242,7 +246,7 @@ spec:
- name: config
configMap:
name: higress-config
{{- if .Values.enableSkywalking }}
{{- if include "skywalking.enabled" . }}
- configMap:
defaultMode: 420
name: higress-custom-bootstrap
@@ -274,3 +278,9 @@ spec:
containerName: higress-gateway
divisor: 1m
resource: limits.cpu
{{- if .Values.global.volumeWasmPlugins }}
- name: local-wasmplugins-volume
hostPath:
path: /opt/plugins
type: Directory
{{- end }}

View File

@@ -17,6 +17,7 @@ global:
local: false # When deploying to a local cluster (e.g.: kind cluster), set this to true.
kind: false # Deprecated. Please use "global.local" instead. Will be removed later.
enableIstioAPI: false
enableGatewayAPI: false
# Deprecated
enableHigressIstio: false
# Used to locate istiod.
@@ -44,8 +45,6 @@ global:
# Releases are published to docker hub under 'istio' project.
# Dev builds from prow are on gcr.io
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
# Default tag for Istio images.
tag: 1.0.0-rc
# Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
@@ -369,7 +368,7 @@ gateway:
name: "higress-gateway"
replicas: 2
image: gateway
tag: "1.0.0-rc"
tag: ""
# revision declares which revision this gateway is a part of
revision: ""
@@ -457,7 +456,7 @@ controller:
name: "higress-controller"
replicas: 1
image: higress
tag: "1.0.0-rc"
tag: ""
env: {}
labels: {}
@@ -547,7 +546,7 @@ pilot:
rollingMaxUnavailable: 25%
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
tag: 1.0.0-rc
tag: ""
# Can be a full hub/image:tag
image: pilot
@@ -560,7 +559,7 @@ pilot:
memory: 2048Mi
env:
PILOT_SCOPE_GATEWAY_TO_NAMESPACE: "true"
PILOT_SCOPE_GATEWAY_TO_NAMESPACE: "false"
PILOT_ENABLE_METADATA_EXCHANGE: "false"
PILOT_ENABLE_CROSS_CLUSTER_WORKLOAD_ENTRY: "false"
VALIDATION_ENABLED: "false"
@@ -583,7 +582,7 @@ pilot:
jwksResolverExtraRootCA: ""
# This is used to set the source of configuration for
# the associated address in configSource, if nothing is specificed
# the associated address in configSource, if nothing is specified
# the default MCP is assumed.
configSource:
subscribedResources: []
@@ -610,7 +609,8 @@ pilot:
# Skywalking config settings
enableSkywalking: false
Skywalking:
address: "skywalking-oap.higress-system.svc"
port: 11800
skywalking:
enabled: false
service:
address: ~
port: 11800

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 1.0.0-rc
version: 1.2.0
- name: higress-console
repository: https://higress.io/helm-charts/
version: 1.0.0-rc
digest: sha256:e22d3f977aec759e23f035bad2cdff643e5f670788872316234701f6d0d91418
generated: "2023-05-08T09:40:35.6095006+08:00"
version: 1.2.0
digest: sha256:d53c2da70cb3bcace50bce756acb50750a84c0888ec0d4c112939bf3c6e4daeb
generated: "2023-09-22T16:52:34.940675+08:00"

View File

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

View File

@@ -40,6 +40,7 @@ The command removes all the Kubernetes components associated with the chart and
| global.disableAlpnH2 | Whether to disable HTTP/2 in ALPN | true |
| global.enableStatus | If `true`, Higress Controller will update the `status` field of Ingress resources.<br />When migrating from Nginx Ingress, in order to avoid `status` field of Ingress objects being overwritten, this parameter needs to be set to false, so Higress won't write the entry IP to the `status` field of the corresponding Ingress object. | true |
| global.enableIstioAPI | If `true`, Higress Controller will monitor istio resources as well | false |
| global.enableGatewayAPI | If `true`, Higress Controller will monitor Gateway API resources as well | false |
| global.istioNamespace | The namespace istio is installed to | istio-system |
| **Core Paramters** | | |
| higress-core.gateway.replicas | Number of Higress Gateway pods | 2 |

View File

@@ -140,7 +140,7 @@ index 25479439b7..bd34442260 100755
# This script builds and version stamps the output
+export GOPROXY="https://proxy.golang.com.cn,direct"
+export GOPROXY=${GOPROXY:-"https://proxy.golang.com.cn,direct"}
+
VERBOSE=${VERBOSE:-"0"}
V=""
@@ -215,10 +215,11 @@ index 271fe77a2d..c9e625efdc 100755
--init \
--sig-proxy=true \
${DOCKER_SOCKET_MOUNT:--v /var/run/docker.sock:/var/run/docker.sock} \
@@ -55,7 +59,15 @@ read -ra DOCKER_RUN_OPTIONS <<< "${DOCKER_RUN_OPTIONS:-}"
@@ -55,7 +59,16 @@ read -ra DOCKER_RUN_OPTIONS <<< "${DOCKER_RUN_OPTIONS:-}"
--env-file <(env | grep -v ${ENV_BLOCKLIST}) \
-e IN_BUILD_CONTAINER=1 \
-e TZ="${TIMEZONE:-$TZ}" \
+ -e GOPROXY="${GOPROXY}" \
+ -e HUB="${HUB}" \
+ -e ENVOY_TAR_PATH="${ENVOY_TAR_PATH}" \
+ --mount "type=bind,source=${MOUNT_PACKAGE_SOURCE},destination=/home/package" \

View File

@@ -0,0 +1,30 @@
diff --color -Naur istio/pilot/pkg/features/pilot.go istio_new/pilot/pkg/features/pilot.go
--- istio/pilot/pkg/features/pilot.go 2023-06-18 20:13:57.715044832 +0800
+++ istio_new/pilot/pkg/features/pilot.go 2023-06-18 20:11:40.310406690 +0800
@@ -359,6 +359,9 @@
EnableUnsafeAdminEndpoints = env.RegisterBoolVar("UNSAFE_ENABLE_ADMIN_ENDPOINTS", false,
"If this is set to true, dangerous admin endpoints will be exposed on the debug interface. Not recommended for production.").Get()
+ DebugAuth = env.RegisterBoolVar("DEBUG_AUTH", true,
+ "If this is set to false, the debug interface will allow all anonymous request from any remote host, which is not recommended for production").Get()
+
XDSAuth = env.RegisterBoolVar("XDS_AUTH", true,
"If true, will authenticate XDS clients.").Get()
diff --color -Naur istio/pilot/pkg/xds/debug.go istio_new/pilot/pkg/xds/debug.go
--- istio/pilot/pkg/xds/debug.go 2023-06-18 20:13:57.695044739 +0800
+++ istio_new/pilot/pkg/xds/debug.go 2023-06-18 20:11:40.286406579 +0800
@@ -218,8 +218,12 @@
if internalMux != nil {
internalMux.HandleFunc(path, handler)
}
+ handlerFunc := http.HandlerFunc(handler)
+ if features.DebugAuth {
+ handlerFunc = s.allowAuthenticatedOrLocalhost(handlerFunc)
+ }
// Add handler with auth; this is expose on an HTTP server
- mux.HandleFunc(path, s.allowAuthenticatedOrLocalhost(http.HandlerFunc(handler)))
+ mux.HandleFunc(path, handlerFunc)
}
func (s *DiscoveryServer) allowAuthenticatedOrLocalhost(next http.Handler) http.HandlerFunc {

View File

@@ -0,0 +1,15 @@
diff -Naur istio/pilot/pkg/xds/debug.go istio-new/pilot/pkg/xds/debug.go
--- istio/pilot/pkg/xds/debug.go 2023-06-27 14:08:00.000000000 +0800
+++ istio-new/pilot/pkg/xds/debug.go 2023-06-27 14:07:04.000000000 +0800
@@ -469,6 +469,11 @@
s.Env.IstioConfigStore.Schemas().ForEach(func(schema collection.Schema) bool {
cfg, _ := s.Env.IstioConfigStore.List(schema.Resource().GroupVersionKind(), "")
// Added by ingress
+ copied := make([]config.Config, len(cfg))
+ for i := range copied {
+ copied[i] = cfg[i].DeepCopy()
+ }
+ cfg = copied
switch schema.Resource().GroupVersionKind().String() {
case gvk.Gateway.String():
cfg = model.GatewayFilter(cfg)

View File

@@ -0,0 +1,124 @@
diff -Naur istio/bin/init.sh istio-new/bin/init.sh
--- istio/bin/init.sh 2023-08-15 21:01:53.601636573 +0800
+++ istio-new/bin/init.sh 2023-08-15 21:04:56.144783484 +0800
@@ -151,7 +151,8 @@
# download_envoy_if_necessary "${ISTIO_ENVOY_LINUX_RELEASE_URL}" "$ISTIO_ENVOY_LINUX_RELEASE_PATH" "${SIDECAR}"
# download_envoy_if_necessary "${ISTIO_ENVOY_CENTOS_RELEASE_URL}" "$ISTIO_ENVOY_CENTOS_LINUX_RELEASE_PATH" "${SIDECAR}-centos"
-untar_envoy_if_necessary "${ENVOY_TAR_PATH}" "$ISTIO_ENVOY_LINUX_RELEASE_PATH" "${SIDECAR}"
+untar_envoy_if_necessary "${ENVOY_TAR_DIR}/envoy-arm64.tar.gz" "$ISTIO_ENVOY_LINUX_ARM64_RELEASE_PATH" "${SIDECAR}"
+untar_envoy_if_necessary "${ENVOY_TAR_DIR}/envoy-amd64.tar.gz" "$ISTIO_ENVOY_LINUX_AMD64_RELEASE_PATH" "${SIDECAR}"
if [[ "$GOOS_LOCAL" == "darwin" ]]; then
# Download and extract the Envoy macOS release binary
diff -Naur istio/common/scripts/run.sh istio-new/common/scripts/run.sh
--- istio/common/scripts/run.sh 2023-08-15 21:01:53.601636573 +0800
+++ istio-new/common/scripts/run.sh 2023-08-15 17:37:57.754600731 +0800
@@ -36,7 +36,7 @@
HUB="${HUB:-istio}"
MOUNT_SOURCE="${MOUNT_SOURCE:-${PWD}}"
-ENVOY_TAR_PATH="${ENVOY_TAR_PATH:-/home/package/envoy.tar.gz}"
+ENVOY_TAR_DIR="${ENVOY_TAR_DIR:-/home/package}"
MOUNT_DEST="${MOUNT_DEST:-/work}"
MOUNT_ROOT_SOURCE="${MOUNT_ROOT_SOURCE:-`cd $MOUNT_SOURCE/..;pwd`}"
MOUNT_PACKAGE_SOURCE="${MOUNT_PACKAGE_SOURCE:-`cd $MOUNT_SOURCE/../package;pwd`}"
@@ -61,7 +61,7 @@
-e TZ="${TIMEZONE:-$TZ}" \
-e GOPROXY="${GOPROXY}" \
-e HUB="${HUB}" \
- -e ENVOY_TAR_PATH="${ENVOY_TAR_PATH}" \
+ -e ENVOY_TAR_DIR="${ENVOY_TAR_DIR}" \
--mount "type=bind,source=${MOUNT_PACKAGE_SOURCE},destination=/home/package" \
--mount "type=bind,source=${MOUNT_SOURCE},destination=/work" \
--mount "type=bind,source=${MOUNT_ROOT_SOURCE}/..,destination=/parent" \
diff -Naur istio/common/scripts/setup_env.sh istio-new/common/scripts/setup_env.sh
--- istio/common/scripts/setup_env.sh 2023-08-15 21:01:53.601636573 +0800
+++ istio-new/common/scripts/setup_env.sh 2023-08-15 20:15:23.292391629 +0800
@@ -81,6 +81,9 @@
export TARGET_OUT="${TARGET_OUT:-$(pwd)/out/${TARGET_OS}_${TARGET_ARCH}}"
export TARGET_OUT_LINUX="${TARGET_OUT_LINUX:-$(pwd)/out/linux_${TARGET_ARCH}}"
+export ARM64_OUT_LINUX=/work/out/linux_arm64
+export AMD64_OUT_LINUX=/work/out/linux_amd64
+
export CONTAINER_TARGET_OUT="${CONTAINER_TARGET_OUT:-/work/out/${TARGET_OS}_${TARGET_ARCH}}"
export CONTAINER_TARGET_OUT_LINUX="${CONTAINER_TARGET_OUT_LINUX:-/work/out/linux_${TARGET_ARCH}}"
diff -Naur istio/Makefile.core.mk istio-new/Makefile.core.mk
--- istio/Makefile.core.mk 2023-08-15 21:01:53.601636573 +0800
+++ istio-new/Makefile.core.mk 2023-08-15 20:03:25.384280274 +0800
@@ -150,6 +150,11 @@
export ISTIO_ENVOY_CENTOS_LINUX_RELEASE_NAME ?= envoy-centos-${ISTIO_ENVOY_LINUX_VERSION}
export ISTIO_ENVOY_CENTOS_LINUX_RELEASE_PATH ?= ${ISTIO_ENVOY_LINUX_RELEASE_DIR}/${ISTIO_ENVOY_CENTOS_LINUX_RELEASE_NAME}
+export ISTIO_ENVOY_LINUX_ARM64_RELEASE_DIR ?= ${ARM64_OUT_LINUX}/release
+export ISTIO_ENVOY_LINUX_ARM64_RELEASE_PATH ?= ${ISTIO_ENVOY_LINUX_ARM64_RELEASE_DIR}/${ISTIO_ENVOY_LINUX_RELEASE_NAME}
+export ISTIO_ENVOY_LINUX_AMD64_RELEASE_DIR ?= ${AMD64_OUT_LINUX}/release
+export ISTIO_ENVOY_LINUX_AMD64_RELEASE_PATH ?= ${ISTIO_ENVOY_LINUX_AMD64_RELEASE_DIR}/${ISTIO_ENVOY_LINUX_RELEASE_NAME}
+
# Envoy macOS vars.
# TODO Change url when official envoy release for macOS is available
export ISTIO_ENVOY_MACOS_VERSION ?= 1.0.2
@@ -240,6 +245,8 @@
${ISTIO_ENVOY_LINUX_DEBUG_PATH}: init
${ISTIO_ENVOY_LINUX_RELEASE_PATH}: init
${ISTIO_ENVOY_MACOS_RELEASE_PATH}: init
+${ISTIO_ENVOY_LINUX_ARM64_RELEASE_PATH}: init
+${ISTIO_ENVOY_LINUX_AMD64_RELEASE_PATH}: init
# Pull dependencies, based on the checked in Gopkg.lock file.
# Developers must manually run `dep ensure` if adding new deps
@@ -312,8 +319,8 @@
# various platform images.
.PHONY: build-linux
build-linux: depend
- GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) common/scripts/gobuild.sh $(ISTIO_OUT_LINUX)/ $(STANDARD_BINARIES)
- GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) common/scripts/gobuild.sh $(ISTIO_OUT_LINUX)/ -tags=agent $(AGENT_BINARIES)
+ GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) GOBUILDFLAGS='-buildvcs=false' common/scripts/gobuild.sh $(ISTIO_OUT_LINUX)/ $(STANDARD_BINARIES)
+ GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) GOBUILDFLAGS='-buildvcs=false' common/scripts/gobuild.sh $(ISTIO_OUT_LINUX)/ -tags=agent $(AGENT_BINARIES)
# Create targets for ISTIO_OUT_LINUX/binary
# There are two use cases here:
diff -Naur istio/tools/istio-docker.mk istio-new/tools/istio-docker.mk
--- istio/tools/istio-docker.mk 2023-08-15 21:01:53.621637356 +0800
+++ istio-new/tools/istio-docker.mk 2023-08-15 20:02:11.881402098 +0800
@@ -77,6 +77,14 @@
${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/envoy_bootstrap.json: ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_PATH}
cp ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_PATH} ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/envoy_bootstrap.json
+${ISTIO_ENVOY_LINUX_ARM64_RELEASE_DIR}/${SIDECAR}: ${ISTIO_ENVOY_LINUX_ARM64_RELEASE_PATH}
+ mkdir -p $(DOCKER_BUILD_TOP)/proxyv2
+ cp ${ISTIO_ENVOY_LINUX_ARM64_RELEASE_PATH} ${ISTIO_ENVOY_LINUX_ARM64_RELEASE_DIR}/${SIDECAR}
+
+${ISTIO_ENVOY_LINUX_AMD64_RELEASE_DIR}/${SIDECAR}: ${ISTIO_ENVOY_LINUX_AMD64_RELEASE_PATH}
+ mkdir -p $(DOCKER_BUILD_TOP)/proxyv2
+ cp ${ISTIO_ENVOY_LINUX_AMD64_RELEASE_PATH} ${ISTIO_ENVOY_LINUX_AMD64_RELEASE_DIR}/${SIDECAR}
+
# rule for wasm extensions.
$(ISTIO_ENVOY_LINUX_RELEASE_DIR)/stats-filter.wasm: init
$(ISTIO_ENVOY_LINUX_RELEASE_DIR)/stats-filter.compiled.wasm: init
@@ -88,7 +96,8 @@
docker.proxyv2: BUILD_ARGS=--build-arg proxy_version=istio-proxy:${PROXY_REPO_SHA} --build-arg istio_version=${VERSION} --build-arg BASE_VERSION=${BASE_VERSION} --build-arg SIDECAR=${SIDECAR} --build-arg HUB=${HUB}
docker.proxyv2: ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/envoy_bootstrap.json
docker.proxyv2: ${ISTIO_ENVOY_BOOTSTRAP_CONFIG_DIR}/gcp_envoy_bootstrap.json
-docker.proxyv2: $(ISTIO_ENVOY_LINUX_RELEASE_DIR)/${SIDECAR}
+docker.proxyv2: ${ISTIO_ENVOY_LINUX_ARM64_RELEASE_DIR}/${SIDECAR}
+docker.proxyv2: ${ISTIO_ENVOY_LINUX_AMD64_RELEASE_DIR}/${SIDECAR}
docker.proxyv2: $(ISTIO_OUT_LINUX)/pilot-agent
docker.proxyv2: pilot/docker/Dockerfile.proxyv2
# docker.proxyv2: $(ISTIO_ENVOY_LINUX_RELEASE_DIR)/stats-filter.wasm
@@ -324,7 +333,13 @@
# This can be done with DOCKER_BUILD_VARIANTS="default debug" as well, but at the expense of building twice vs building once and tagging twice
INCLUDE_UNTAGGED_DEFAULT ?= false
DEFAULT_DISTRIBUTION=debug
+
+
+ifeq ($(BUILDX_PLATFORM), true)
+DOCKER_RULE ?= $(foreach VARIANT,$(DOCKER_BUILD_VARIANTS), time (mkdir -p $(DOCKER_BUILD_TOP)/$@ && TARGET_ARCH=$(TARGET_ARCH) ./tools/docker-copy.sh $^ $(DOCKER_BUILD_TOP)/$@ && cd $(DOCKER_BUILD_TOP)/$@ $(BUILD_PRE) && docker buildx create --use && docker buildx build --no-cache --platform linux/amd64,linux/arm64 $(BUILD_ARGS) --build-arg BASE_DISTRIBUTION=$(call normalize-tag,$(VARIANT)) -t $(HUB)/$(subst docker.,,$@):$(TAG)$(call variant-tag,$(VARIANT)) -f Dockerfile$(suffix $@) . --push ); )
+else
DOCKER_RULE ?= $(foreach VARIANT,$(DOCKER_BUILD_VARIANTS), time (mkdir -p $(DOCKER_BUILD_TOP)/$@ && TARGET_ARCH=$(TARGET_ARCH) ./tools/docker-copy.sh $^ $(DOCKER_BUILD_TOP)/$@ && cd $(DOCKER_BUILD_TOP)/$@ $(BUILD_PRE) && docker build $(BUILD_ARGS) --build-arg BASE_DISTRIBUTION=$(call normalize-tag,$(VARIANT)) -t $(HUB)/$(subst docker.,,$@):$(TAG)$(call variant-tag,$(VARIANT)) -f Dockerfile$(suffix $@) . ); )
+endif
RENAME_TEMPLATE ?= mkdir -p $(DOCKER_BUILD_TOP)/$@ && cp $(ECHO_DOCKER)/$(VM_OS_DOCKERFILE_TEMPLATE) $(DOCKER_BUILD_TOP)/$@/Dockerfile$(suffix $@)
# This target will package all docker images used in test and release, without re-building

View File

@@ -0,0 +1,14 @@
diff -Naur istio/pilot/pkg/config/kube/gateway/conversion.go istio_new/pilot/pkg/config/kube/gateway/conversion.go
--- istio/pilot/pkg/config/kube/gateway/conversion.go 2023-09-22 11:06:50.400535200 +0800
+++ istio_new/pilot/pkg/config/kube/gateway/conversion.go 2023-09-22 11:07:52.954982700 +0800
@@ -37,8 +37,8 @@
)
const (
- DefaultClassName = "istio"
- ControllerName = "istio.io/gateway-controller"
+ DefaultClassName = "higress"
+ ControllerName = "higress.io/gateway-controller"
)
// KubernetesResources stores all inputs to our conversion

View File

@@ -20,6 +20,10 @@ import (
"net/http"
"time"
"github.com/alibaba/higress/pkg/ingress/kube/common"
"github.com/alibaba/higress/pkg/ingress/mcp"
"github.com/alibaba/higress/pkg/ingress/translation"
higresskube "github.com/alibaba/higress/pkg/kube"
prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
@@ -46,11 +50,6 @@ import (
"istio.io/pkg/log"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
ingressconfig "github.com/alibaba/higress/pkg/ingress/config"
"github.com/alibaba/higress/pkg/ingress/kube/common"
"github.com/alibaba/higress/pkg/ingress/mcp"
higresskube "github.com/alibaba/higress/pkg/kube"
)
type XdsOptions struct {
@@ -225,9 +224,12 @@ func (s *Server) initConfigController() error {
if options.ClusterId == "Kubernetes" {
options.ClusterId = ""
}
ingressConfig := ingressconfig.NewIngressConfig(s.kubeClient, s.xdsServer, ns, options.ClusterId)
ingressController := ingressConfig.AddLocalCluster(options)
ingressConfig := translation.NewIngressTranslation(s.kubeClient, s.xdsServer, ns, options.ClusterId)
ingressController, kingressController := ingressConfig.AddLocalCluster(options)
s.configStores = append(s.configStores, ingressConfig)
// Wrap the config controller with a cache.
aggregateConfigController, err := configaggregate.MakeCache(s.configStores)
if err != nil {
@@ -242,7 +244,7 @@ func (s *Server) initConfigController() error {
// Defer starting the controller until after the service is created.
s.server.RunComponent(func(stop <-chan struct{}) error {
if err := ingressConfig.InitializeCluster(ingressController, stop); err != nil {
if err := ingressConfig.InitializeCluster(ingressController, kingressController, stop); err != nil {
return err
}
go s.configController.Run(stop)

View File

@@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package kubernetes
package hgctl
import (
_ "github.com/alibaba/higress/test/ingress/conformance/utils/flags"
const (
yamlOutput = "yaml"
jsonOutput = "json"
flagsOutput = "flags"
)

View File

@@ -33,8 +33,8 @@ var (
)
const (
adminPort = 15000
containerName = "envoy"
defaultProxyAdminPort = 15000
containerName = "envoy"
)
func retrieveConfigDump(args []string, includeEds bool) ([]byte, error) {
@@ -96,7 +96,7 @@ func portForwarder(nn types.NamespacedName) (kubernetes.PortForwarder, error) {
return nil, fmt.Errorf("pod %s is not running", nn)
}
fw, err := kubernetes.NewLocalPortForwarder(c, nn, 0, adminPort)
fw, err := kubernetes.NewLocalPortForwarder(c, nn, 0, defaultProxyAdminPort)
if err != nil {
return nil, err
}

View File

@@ -34,6 +34,7 @@ type fakePortForwarder struct {
localPort int
l net.Listener
mux *http.ServeMux
stopCh chan struct{}
}
func newFakePortForwarder(b []byte) (kubernetes.PortForwarder, error) {
@@ -46,6 +47,7 @@ func newFakePortForwarder(b []byte) (kubernetes.PortForwarder, error) {
responseBody: b,
localPort: p,
mux: http.NewServeMux(),
stopCh: make(chan struct{}),
}
fw.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(fw.responseBody)
@@ -54,6 +56,10 @@ func newFakePortForwarder(b []byte) (kubernetes.PortForwarder, error) {
return fw, nil
}
func (fw *fakePortForwarder) WaitForStop() {
<-fw.stopCh
}
func (fw *fakePortForwarder) Start() error {
l, err := net.Listen("tcp", fw.Address())
if err != nil {

345
pkg/cmd/hgctl/dashboard.go Normal file
View File

@@ -0,0 +1,345 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
import (
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"runtime"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
)
var (
listenPort = 0
promPort = 0
grafanaPort = 0
consolePort = 0
bindAddress = "localhost"
// open browser or not, default is true
browser = true
// label selector
labelSelector = ""
addonNamespace = ""
envoyDashNs = ""
proxyAdminPort int
)
const (
defaultPrometheusPort = 9090
defaultGrafanaPort = 3000
defaultConsolePort = 8080
)
func newDashboardCmd() *cobra.Command {
dashboardCmd := &cobra.Command{
Use: "dashboard",
Aliases: []string{"dash", "d"},
Short: "Access to Higress web UIs",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 0 {
return fmt.Errorf("unknown dashboard %q", args[0])
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
cmd.HelpFunc()(cmd, args)
return nil
},
}
dashboardCmd.PersistentFlags().IntVarP(&listenPort, "port", "p", 0, "Local port to listen to")
dashboardCmd.PersistentFlags().BoolVar(&browser, "browser", true,
"When --browser is supplied as false, hgctl dashboard will not open the browser. "+
"Default is true which means hgctl dashboard will always open a browser to view the dashboard.")
dashboardCmd.PersistentFlags().StringVarP(&addonNamespace, "namespace", "n", "higress-system",
"Namespace where the addon is running, if not specified, higress-system would be used")
prom := promDashCmd()
prom.PersistentFlags().IntVar(&promPort, "ui-port", defaultPrometheusPort, "The component dashboard UI port.")
dashboardCmd.AddCommand(prom)
graf := grafanaDashCmd()
graf.PersistentFlags().IntVar(&grafanaPort, "ui-port", defaultGrafanaPort, "The component dashboard UI port.")
dashboardCmd.AddCommand(graf)
envoy := envoyDashCmd()
envoy.PersistentFlags().StringVarP(&labelSelector, "selector", "l", "app=higress-gateway", "Label selector")
envoy.PersistentFlags().StringVarP(&envoyDashNs, "namespace", "n", "",
"Namespace where the addon is running, if not specified, higress-system would be used")
envoy.PersistentFlags().IntVar(&proxyAdminPort, "ui-port", defaultProxyAdminPort, "The component dashboard UI port.")
dashboardCmd.AddCommand(envoy)
consoleCmd := consoleDashCmd()
consoleCmd.PersistentFlags().IntVar(&consolePort, "ui-port", defaultConsolePort, "The component dashboard UI port.")
dashboardCmd.AddCommand(consoleCmd)
return dashboardCmd
}
// port-forward to Higress System Prometheus; open browser
func promDashCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "prometheus",
Short: "Open Prometheus web UI",
Long: `Open Higress's Prometheus dashboard`,
Example: ` hgctl dashboard prometheus
# with short syntax
hgctl dash prometheus
hgctl d prometheus`,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("build CLI client fail: %w", err)
}
pl, err := client.PodsForSelector(addonNamespace, "app=higress-console-prometheus")
if err != nil {
return fmt.Errorf("not able to locate Prometheus pod: %v", err)
}
if len(pl.Items) < 1 {
return errors.New("no Prometheus pods found")
}
// only use the first pod in the list
return portForward(pl.Items[0].Name, addonNamespace, "Prometheus",
"http://%s", bindAddress, promPort, client, cmd.OutOrStdout(), browser)
},
}
return cmd
}
// port-forward to Higress System Console; open browser
func consoleDashCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "console",
Short: "Open Console web UI",
Long: `Open Higress Console`,
Example: ` hgctl dashboard console
# with short syntax
hgctl dash console
hgctl d console`,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("build CLI client fail: %w", err)
}
pl, err := client.PodsForSelector(addonNamespace, "app.kubernetes.io/name=higress-console")
if err != nil {
return fmt.Errorf("not able to locate console pod: %v", err)
}
if len(pl.Items) < 1 {
return errors.New("no higress console pods found")
}
// only use the first pod in the list
return portForward(pl.Items[0].Name, addonNamespace, "Console",
"http://%s", bindAddress, consolePort, client, cmd.OutOrStdout(), browser)
},
}
return cmd
}
// port-forward to Higress System Grafana; open browser
func grafanaDashCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "grafana",
Short: "Open Grafana web UI",
Long: `Open Higress's Grafana dashboard`,
Example: ` hgctl dashboard grafana
# with short syntax
hgctl dash grafana
hgctl d grafana`,
RunE: func(cmd *cobra.Command, args []string) error {
client, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("build CLI client fail: %w", err)
}
pl, err := client.PodsForSelector(addonNamespace, "app=higress-console-grafana")
if err != nil {
return fmt.Errorf("not able to locate Grafana pod: %v", err)
}
if len(pl.Items) < 1 {
return errors.New("no Grafana pods found")
}
// only use the first pod in the list
return portForward(pl.Items[0].Name, addonNamespace, "Grafana",
"http://%s", bindAddress, grafanaPort, client, cmd.OutOrStdout(), browser)
},
}
return cmd
}
// port-forward to sidecar Envoy admin port; open browser
func envoyDashCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "envoy [<type>/]<name>[.<namespace>]",
Short: "Open Envoy admin web UI",
Long: `Open the Envoy admin dashboard for a higress gateway`,
Example: ` # Open Envoy dashboard for the higress-gateway-56f9b9797-b9nnc
hgctl dashboard envoy higress-gateway-56f9b9797-b9nnc
# with short syntax
hgctl dash envoy
hgctl d envoy
`,
RunE: func(c *cobra.Command, args []string) error {
kubeClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("build CLI client fail: %w", err)
}
if labelSelector == "" && len(args) < 1 {
c.Println(c.UsageString())
return fmt.Errorf("specify a pod or --selector")
}
if err != nil {
return fmt.Errorf("failed to create k8s client: %v", err)
}
var podName, ns string
if labelSelector != "" {
pl, err := kubeClient.PodsForSelector(envoyDashNs, labelSelector)
if err != nil {
return fmt.Errorf("not able to locate pod with selector %s: %v", labelSelector, err)
}
if len(pl.Items) < 1 {
return errors.New("no pods found")
}
// only use the first pod in the list
podName = pl.Items[0].Name
ns = pl.Items[0].Namespace
} else if len(args) > 0 {
po, err := kubeClient.Pod(types.NamespacedName{Name: args[0], Namespace: envoyDashNs})
if err != nil {
return err
}
podName = po.Name
ns = po.Namespace
}
return portForward(podName, ns, fmt.Sprintf("Envoy sidecar %s", podName),
"http://%s", bindAddress, proxyAdminPort, kubeClient, c.OutOrStdout(), browser)
},
}
return cmd
}
// portForward first tries to forward localhost:remotePort to podName:remotePort, falls back to dynamic local port
func portForward(podName, namespace, flavor, urlFormat, localAddress string, remotePort int,
client kubernetes.CLIClient, writer io.Writer, browser bool,
) error {
// port preference:
// - If --listenPort is specified, use it
// - without --listenPort, prefer the remotePort but fall back to a random port
var portPrefs []int
if listenPort != 0 {
portPrefs = []int{listenPort}
} else {
portPrefs = []int{remotePort}
}
var err error
for _, localPort := range portPrefs {
var fw kubernetes.PortForwarder
fw, err = kubernetes.NewLocalPortForwarder(client, types.NamespacedName{Namespace: namespace, Name: podName}, localPort, remotePort)
if err != nil {
return fmt.Errorf("could not build port forwarder for %s: %v", flavor, err)
}
if err := fw.Start(); err != nil {
fw.Stop()
// Try the next port
continue
}
// Close the port forwarder when the command is terminated.
ClosePortForwarderOnInterrupt(fw)
openBrowser(fmt.Sprintf(urlFormat, fw.Address()), writer, browser)
// Wait for stop
fw.WaitForStop()
}
if err != nil {
return fmt.Errorf("failure running port forward process: %v", err)
}
return nil
}
func ClosePortForwarderOnInterrupt(fw kubernetes.PortForwarder) {
go func() {
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
<-signals
fw.Stop()
}()
}
func openBrowser(url string, writer io.Writer, browser bool) {
var err error
fmt.Fprintf(writer, "%s\n", url)
if !browser {
fmt.Fprint(writer, "skipping opening a browser")
return
}
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
fmt.Fprintf(writer, "Unsupported platform %q; open %s in your browser.\n", runtime.GOOS, url)
}
if err != nil {
fmt.Fprintf(writer, "Failed to open browser; open %s in your browser.\nError: %s\n", url, err.Error())
}
}

View File

@@ -0,0 +1,297 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helm
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/tpath"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"sigs.k8s.io/yaml"
)
// ReadYamlProfile gets the overlay yaml file from list of files and return profile value from file overlay and set overlay.
func ReadYamlProfile(inFilenames []string, setFlags []string) (string, string, error) {
profileName := DefaultProfileName
// The profile coming from --set flag has the highest precedence.
psf := GetValueForSetFlag(setFlags, "profile")
if psf != "" {
profileName = psf
}
return "", profileName, nil
}
func GetUninstallProfileName() string {
return DefaultUninstallProfileName
}
func ReadLayeredYAMLs(filenames []string) (string, error) {
return readLayeredYAMLs(filenames, os.Stdin)
}
func readLayeredYAMLs(filenames []string, stdinReader io.Reader) (string, error) {
var ly string
var stdin bool
for _, fn := range filenames {
var b []byte
var err error
if fn == "-" {
if stdin {
continue
}
stdin = true
b, err = io.ReadAll(stdinReader)
} else {
b, err = os.ReadFile(strings.TrimSpace(fn))
}
if err != nil {
return "", err
}
ly, err = util.OverlayYAML(ly, string(b))
if err != nil {
return "", err
}
}
return ly, nil
}
// GetValueForSetFlag parses the passed set flags which have format key=value and if any set the given path,
// returns the corresponding value, otherwise returns the empty string. setFlags must have valid format.
func GetValueForSetFlag(setFlags []string, path string) string {
ret := ""
for _, sf := range setFlags {
p, v := getPV(sf)
if p == path {
ret = v
}
// if set multiple times, return last set value
}
return ret
}
// getPV returns the path and value components for the given set flag string, which must be in path=value format.
func getPV(setFlag string) (path string, value string) {
pv := strings.Split(setFlag, "=")
if len(pv) != 2 {
return setFlag, ""
}
path, value = strings.TrimSpace(pv[0]), strings.TrimSpace(pv[1])
return
}
func GenerateConfig(inFilenames []string, setFlags []string) (string, *Profile, string, error) {
if err := validateSetFlags(setFlags); err != nil {
return "", nil, "", err
}
fy, profileName, err := ReadYamlProfile(inFilenames, setFlags)
if err != nil {
return "", nil, "", err
}
profileString, profile, err := GenProfile(profileName, fy, setFlags)
if err != nil {
return "", nil, "", err
}
return profileString, profile, profileName, nil
}
// validateSetFlags validates that setFlags all have path=value format.
func validateSetFlags(setFlags []string) error {
for _, sf := range setFlags {
pv := strings.Split(sf, "=")
if len(pv) != 2 {
return fmt.Errorf("set flag %s has incorrect format, must be path=value", sf)
}
}
return nil
}
func overlaySetFlagValues(iopYAML string, setFlags []string) (string, error) {
iop := make(map[string]any)
if err := yaml.Unmarshal([]byte(iopYAML), &iop); err != nil {
return "", err
}
// Unmarshal returns nil for empty manifests but we need something to insert into.
if iop == nil {
iop = make(map[string]any)
}
for _, sf := range setFlags {
p, v := getPV(sf)
inc, _, err := tpath.GetPathContext(iop, util.PathFromString(p), true)
if err != nil {
return "", err
}
// input value type is always string, transform it to correct type before setting.
if err := tpath.WritePathContext(inc, util.ParseValue(v), false); err != nil {
return "", err
}
}
out, err := yaml.Marshal(iop)
if err != nil {
return "", err
}
return string(out), nil
}
// getInstallPackagePath returns the installPackagePath in the given IstioOperator YAML string.
func getInstallPackagePath(profileYAML string) (string, error) {
profile, err := UnmarshalProfile(profileYAML)
if err != nil {
return "", err
}
if profile == nil {
return "", nil
}
return profile.InstallPackagePath, nil
}
// GetProfileYAML returns the YAML for the given profile name, using the given profileOrPath string, which may be either
// a profile label or a file path.
func GetProfileYAML(installPackagePath, profileOrPath string) (string, error) {
if profileOrPath == "" {
profileOrPath = DefaultProfileFilename
}
profiles, err := readProfiles(installPackagePath)
if err != nil {
return "", fmt.Errorf("failed to read profiles: %v", err)
}
// If charts are a file path and profile is a name like default, transform it to the file path.
if profiles[profileOrPath] && installPackagePath != "" {
profileOrPath = filepath.Join(installPackagePath, "profiles", profileOrPath+".yaml")
}
// This contains the IstioOperator CR.
baseCRYAML, err := ReadProfileYAML(profileOrPath, installPackagePath)
if err != nil {
return "", err
}
//if !IsDefaultProfile(profileOrPath) {
// // Profile definitions are relative to the default profileOrPath, so read that first.
// dfn := DefaultFilenameForProfile(profileOrPath)
// defaultYAML, err := ReadProfileYAML(dfn, installPackagePath)
// if err != nil {
// return "", err
// }
// baseCRYAML, err = util.OverlayYAML(defaultYAML, baseCRYAML)
// if err != nil {
// return "", err
// }
//}
return baseCRYAML, nil
}
// IsDefaultProfile reports whether the given profile is the default profile.
func IsDefaultProfile(profile string) bool {
return profile == "" || profile == DefaultProfileName || filepath.Base(profile) == DefaultProfileFilename
}
// DefaultFilenameForProfile returns the profile name of the default profile for the given profile.
func DefaultFilenameForProfile(profile string) string {
switch {
case util.IsFilePath(profile):
return filepath.Join(filepath.Dir(profile), DefaultProfileFilename)
default:
return DefaultProfileName
}
}
// ReadProfileYAML reads the YAML values associated with the given profile. It uses an appropriate reader for the
// profile format (compiled-in, file, HTTP, etc.).
func ReadProfileYAML(profile, manifestsPath string) (string, error) {
var err error
var globalValues string
// Get global values from profile.
switch {
case util.IsFilePath(profile):
if globalValues, err = readFile(profile); err != nil {
return "", err
}
default:
if globalValues, err = LoadValues(profile, manifestsPath); err != nil {
return "", fmt.Errorf("failed to read profile %v from %v: %v", profile, manifestsPath, err)
}
}
return globalValues, nil
}
func readFile(path string) (string, error) {
b, err := os.ReadFile(path)
return string(b), err
}
// UnmarshalProfile unmarshals a string containing Profile as YAML.
func UnmarshalProfile(profileYAML string) (*Profile, error) {
profile := &Profile{}
if err := yaml.Unmarshal([]byte(profileYAML), profile); err != nil {
return nil, fmt.Errorf("%s:\n\nYAML:\n%s", err, profileYAML)
}
return profile, nil
}
// GenProfile generates an Profile from the given profile name or path, and overlay YAMLs from user
// files and the --set flag. If successful, it returns an Profile string and struct.
func GenProfile(profileOrPath, fileOverlayYAML string, setFlags []string) (string, *Profile, error) {
installPackagePath, err := getInstallPackagePath(fileOverlayYAML)
if err != nil {
return "", nil, err
}
if sfp := GetValueForSetFlag(setFlags, "installPackagePath"); sfp != "" {
// set flag installPackagePath has the highest precedence, if set.
installPackagePath = sfp
}
// To generate the base profileOrPath for overlaying with user values, we need the installPackagePath where the profiles
// can be found, and the selected profileOrPath. Both of these can come from either the user overlay file or --set flag.
outYAML, err := GetProfileYAML(installPackagePath, profileOrPath)
if err != nil {
return "", nil, err
}
// Combine file and --set overlays and translate any K8s settings in values to Profile format
overlayYAML, err := overlaySetFlagValues(fileOverlayYAML, setFlags)
if err != nil {
return "", nil, err
}
// Merge user file and --set flags.
outYAML, err = util.OverlayYAML(outYAML, overlayYAML)
if err != nil {
return "", nil, fmt.Errorf("could not overlay user config over base: %s", err)
}
finalProfile, err := UnmarshalProfile(outYAML)
if err != nil {
return "", nil, err
}
finalProfile.InstallPackagePath = installPackagePath
if finalProfile.Profile == "" {
finalProfile.Profile = DefaultProfileName
}
return util.ToYAML(finalProfile), finalProfile, nil
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package name
// Kubernetes Kind strings.
const (
CRDStr = "CustomResourceDefinition"
ClusterRoleStr = "ClusterRole"
ClusterRoleBindingStr = "ClusterRoleBinding"
CMStr = "ConfigMap"
DaemonSetStr = "DaemonSet"
DeploymentStr = "Deployment"
EndpointStr = "Endpoints"
HPAStr = "HorizontalPodAutoscaler"
IngressStr = "Ingress"
IstioOperator = "IstioOperator"
MutatingWebhookConfigurationStr = "MutatingWebhookConfiguration"
NamespaceStr = "Namespace"
PVCStr = "PersistentVolumeClaim"
PodStr = "Pod"
PDBStr = "PodDisruptionBudget"
ReplicationControllerStr = "ReplicationController"
ReplicaSetStr = "ReplicaSet"
RoleStr = "Role"
RoleBindingStr = "RoleBinding"
SAStr = "ServiceAccount"
ServiceStr = "Service"
SecretStr = "Secret"
StatefulSetStr = "StatefulSet"
ValidatingWebhookConfigurationStr = "ValidatingWebhookConfiguration"
)
// Istio Kind strings
const (
EnvoyFilterStr = "EnvoyFilter"
GatewayStr = "Gateway"
DestinationRuleStr = "DestinationRule"
MeshPolicyStr = "MeshPolicy"
PeerAuthenticationStr = "PeerAuthentication"
VirtualServiceStr = "VirtualService"
IstioOperatorStr = "IstioOperator"
)
// Istio API Group Names
const (
AuthenticationAPIGroupName = "authentication.istio.io"
ConfigAPIGroupName = "config.istio.io"
NetworkingAPIGroupName = "networking.istio.io"
OperatorAPIGroupName = "operator.istio.io"
SecurityAPIGroupName = "security.istio.io"
)

View File

@@ -0,0 +1,573 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bufio"
"bytes"
"fmt"
"sort"
"strings"
names "github.com/alibaba/higress/pkg/cmd/hgctl/helm/name"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/tpath"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml"
)
const (
// YAMLSeparator is a separator for multi-document YAML files.
YAMLSeparator = "\n---\n"
)
// K8sObject is an in-memory representation of a k8s object, used for moving between different representations
// (Unstructured, JSON, YAML) with cached rendering.
type K8sObject struct {
object *unstructured.Unstructured
Group string
Kind string
Name string
Namespace string
json []byte
yaml []byte
}
// NewK8sObject creates a new K8sObject and returns a ptr to it.
func NewK8sObject(u *unstructured.Unstructured, json, yaml []byte) *K8sObject {
o := &K8sObject{
object: u,
json: json,
yaml: yaml,
}
gvk := u.GetObjectKind().GroupVersionKind()
o.Group = gvk.Group
o.Kind = gvk.Kind
o.Name = u.GetName()
o.Namespace = u.GetNamespace()
return o
}
// Hash returns a unique, insecure hash based on kind, namespace and name.
func Hash(kind, namespace, name string) string {
switch kind {
case names.ClusterRoleStr, names.ClusterRoleBindingStr, names.MeshPolicyStr:
namespace = ""
}
return strings.Join([]string{kind, namespace, name}, ":")
}
// FromHash parses kind, namespace and name from a hash.
func FromHash(hash string) (kind, namespace, name string) {
hv := strings.Split(hash, ":")
if len(hv) != 3 {
return "Bad hash string: " + hash, "", ""
}
kind, namespace, name = hv[0], hv[1], hv[2]
return
}
// HashNameKind returns a unique, insecure hash based on kind and name.
func HashNameKind(kind, name string) string {
return strings.Join([]string{kind, name}, ":")
}
// ParseJSONToK8sObject parses JSON to an K8sObject.
func ParseJSONToK8sObject(json []byte) (*K8sObject, error) {
o, _, err := unstructured.UnstructuredJSONScheme.Decode(json, nil, nil)
if err != nil {
return nil, fmt.Errorf("error parsing json into unstructured object: %v", err)
}
u, ok := o.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("parsed unexpected type %T", o)
}
return NewK8sObject(u, json, nil), nil
}
// ParseYAMLToK8sObject parses YAML to an Object.
func ParseYAMLToK8sObject(yaml []byte) (*K8sObject, error) {
r := bytes.NewReader(yaml)
decoder := k8syaml.NewYAMLOrJSONDecoder(r, 1024)
out := &unstructured.Unstructured{}
err := decoder.Decode(out)
if err != nil {
return nil, fmt.Errorf("error decoding object %v: %v", string(yaml), err)
}
return NewK8sObject(out, nil, yaml), nil
}
// UnstructuredObject exposes the raw object, primarily for testing
func (o *K8sObject) UnstructuredObject() *unstructured.Unstructured {
return o.object
}
// ResolveK8sConflict - This method resolves k8s object possible
// conflicting settings. Which K8sObjects may need such method
// depends on the type of the K8sObject.
func (o *K8sObject) ResolveK8sConflict() *K8sObject {
if o.Kind == names.PDBStr {
return resolvePDBConflict(o)
}
return o
}
// Unstructured exposes the raw object content, primarily for testing
func (o *K8sObject) Unstructured() map[string]any {
return o.UnstructuredObject().UnstructuredContent()
}
// Container returns a container subtree for Deployment objects if one is found, or nil otherwise.
func (o *K8sObject) Container(name string) map[string]any {
u := o.Unstructured()
path := fmt.Sprintf("spec.template.spec.containers.[name:%s]", name)
node, f, err := tpath.GetPathContext(u, util.PathFromString(path), false)
if err == nil && f {
// Must be the type from the schema.
return node.Node.(map[string]any)
}
return nil
}
// GroupVersionKind returns the GroupVersionKind for the K8sObject
func (o *K8sObject) GroupVersionKind() schema.GroupVersionKind {
return o.object.GroupVersionKind()
}
// Version returns the APIVersion of the K8sObject
func (o *K8sObject) Version() string {
return o.object.GetAPIVersion()
}
// Hash returns a unique hash for the K8sObject
func (o *K8sObject) Hash() string {
return Hash(o.Kind, o.Namespace, o.Name)
}
// HashNameKind returns a hash for the K8sObject based on the name and kind only.
func (o *K8sObject) HashNameKind() string {
return HashNameKind(o.Kind, o.Name)
}
// JSON returns a JSON representation of the K8sObject, using an internal cache.
func (o *K8sObject) JSON() ([]byte, error) {
if o.json != nil {
return o.json, nil
}
b, err := o.object.MarshalJSON()
if err != nil {
return nil, err
}
return b, nil
}
// YAML returns a YAML representation of the K8sObject, using an internal cache.
func (o *K8sObject) YAML() ([]byte, error) {
if o == nil {
return nil, nil
}
if o.yaml != nil {
return o.yaml, nil
}
oj, err := o.JSON()
if err != nil {
return nil, err
}
o.json = oj
y, err := yaml.JSONToYAML(oj)
if err != nil {
return nil, err
}
o.yaml = y
return y, nil
}
// YAMLDebugString returns a YAML representation of the K8sObject, or an error string if the K8sObject cannot be rendered to YAML.
func (o *K8sObject) YAMLDebugString() string {
y, err := o.YAML()
if err != nil {
return err.Error()
}
return string(y)
}
// K8sObjects holds a collection of k8s objects, so that we can filter / sequence them
type K8sObjects []*K8sObject
// String implements the Stringer interface.
func (os K8sObjects) String() string {
var out []string
for _, oo := range os {
out = append(out, oo.YAMLDebugString())
}
return strings.Join(out, YAMLSeparator)
}
// Keys returns a slice with the keys of os.
func (os K8sObjects) Keys() []string {
var out []string
for _, oo := range os {
out = append(out, oo.Hash())
}
return out
}
// UnstructuredItems returns the list of items of unstructured.Unstructured.
func (os K8sObjects) UnstructuredItems() []unstructured.Unstructured {
var usList []unstructured.Unstructured
for _, obj := range os {
usList = append(usList, *obj.UnstructuredObject())
}
return usList
}
// ParseK8sObjectsFromYAMLManifest returns a K8sObjects representation of manifest.
func ParseK8sObjectsFromYAMLManifest(manifest string) (K8sObjects, error) {
return ParseK8sObjectsFromYAMLManifestFailOption(manifest, true)
}
// ParseK8sObjectsFromYAMLManifestFailOption returns a K8sObjects representation of manifest. Continues parsing when a bad object
// is found if failOnError is set to false.
func ParseK8sObjectsFromYAMLManifestFailOption(manifest string, failOnError bool) (K8sObjects, error) {
var b bytes.Buffer
var yamls []string
scanner := bufio.NewScanner(strings.NewReader(manifest))
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "---") {
// yaml separator
yamls = append(yamls, b.String())
b.Reset()
} else {
if _, err := b.WriteString(line); err != nil {
return nil, err
}
if _, err := b.WriteString("\n"); err != nil {
return nil, err
}
}
}
yamls = append(yamls, b.String())
var objects K8sObjects
for _, yaml := range yamls {
yaml = removeNonYAMLLines(yaml)
if yaml == "" {
continue
}
o, err := ParseYAMLToK8sObject([]byte(yaml))
if err != nil {
e := fmt.Errorf("failed to parse YAML to a k8s object: %s", err)
if failOnError {
return nil, e
}
continue
}
if o.Valid() {
objects = append(objects, o)
}
}
return objects, nil
}
func removeNonYAMLLines(yms string) string {
var b strings.Builder
for _, s := range strings.Split(yms, "\n") {
if strings.HasPrefix(s, "#") {
continue
}
b.WriteString(s)
b.WriteString("\n")
}
// helm charts sometimes emits blank objects with just a "disabled" comment.
return strings.TrimSpace(b.String())
}
// YAMLManifest returns a YAML representation of K8sObjects os.
func (os K8sObjects) YAMLManifest() (string, error) {
var b bytes.Buffer
for i, item := range os {
if i != 0 {
if _, err := b.WriteString("\n\n"); err != nil {
return "", err
}
}
ym, err := item.YAML()
if err != nil {
return "", fmt.Errorf("error building yaml: %v", err)
}
if _, err := b.Write(ym); err != nil {
return "", err
}
if _, err := b.Write([]byte(YAMLSeparator)); err != nil {
return "", err
}
}
return b.String(), nil
}
// Sort will order the items in K8sObjects in order of score, group, kind, name. The intent is to
// have a deterministic ordering in which K8sObjects are applied.
func (os K8sObjects) Sort(score func(o *K8sObject) int) {
sort.Slice(os, func(i, j int) bool {
iScore := score(os[i])
jScore := score(os[j])
return iScore < jScore ||
(iScore == jScore &&
os[i].Group < os[j].Group) ||
(iScore == jScore &&
os[i].Group == os[j].Group &&
os[i].Kind < os[j].Kind) ||
(iScore == jScore &&
os[i].Group == os[j].Group &&
os[i].Kind == os[j].Kind &&
os[i].Name < os[j].Name)
})
}
// ToMap returns a map of K8sObject hash to K8sObject.
func (os K8sObjects) ToMap() map[string]*K8sObject {
ret := make(map[string]*K8sObject)
for _, oo := range os {
if oo.Valid() {
ret[oo.Hash()] = oo
}
}
return ret
}
// ToNameKindMap returns a map of K8sObject name/kind hash to K8sObject.
func (os K8sObjects) ToNameKindMap() map[string]*K8sObject {
ret := make(map[string]*K8sObject)
for _, oo := range os {
if oo.Valid() {
ret[oo.HashNameKind()] = oo
}
}
return ret
}
// Valid checks returns true if Kind of K8sObject is not empty.
func (o *K8sObject) Valid() bool {
return o.Kind != ""
}
// FullName returns namespace/name of K8s object
func (o *K8sObject) FullName() string {
return fmt.Sprintf("%s/%s", o.Namespace, o.Name)
}
// Equal returns true if o and other are both valid and equal to each other.
func (o *K8sObject) Equal(other *K8sObject) bool {
if o == nil {
return other == nil
}
if other == nil {
return o == nil
}
ay, err := o.YAML()
if err != nil {
return false
}
by, err := other.YAML()
if err != nil {
return false
}
return util.IsYAMLEqual(string(ay), string(by))
}
func istioCustomResources(group string) bool {
switch group {
case names.ConfigAPIGroupName,
names.SecurityAPIGroupName,
names.AuthenticationAPIGroupName,
names.NetworkingAPIGroupName:
return true
}
return false
}
// DefaultObjectOrder is default sorting function used to sort k8s objects.
func DefaultObjectOrder() func(o *K8sObject) int {
return func(o *K8sObject) int {
gk := o.Group + "/" + o.Kind
switch {
// Create CRDs asap - both because they are slow and because we will likely create instances of them soon
case gk == "apiextensions.k8s.io/CustomResourceDefinition":
return -1000
// We need to create ServiceAccounts, Roles before we bind them with a RoleBinding
case gk == "/ServiceAccount" || gk == "rbac.authorization.k8s.io/ClusterRole":
return 1
case gk == "rbac.authorization.k8s.io/ClusterRoleBinding":
return 2
// validatingwebhookconfiguration is configured to FAIL-OPEN in the default install. For the
// re-install case we want to apply the validatingwebhookconfiguration first to reset any
// orphaned validatingwebhookconfiguration that is FAIL-CLOSE.
case gk == "admissionregistration.k8s.io/ValidatingWebhookConfiguration":
return 3
case istioCustomResources(o.Group):
return 4
// Pods might need configmap or secrets - avoid backoff by creating them first
case gk == "/ConfigMap" || gk == "/Secrets":
return 100
// Create the pods after we've created other things they might be waiting for
case gk == "extensions/Deployment" || gk == "app/Deployment":
return 1000
// Autoscalers typically act on a deployment
case gk == "autoscaling/HorizontalPodAutoscaler":
return 1001
// Create services late - after pods have been started
case gk == "/Service":
return 10000
default:
return 1000
}
}
}
func ObjectsNotInLists(objects K8sObjects, lists ...K8sObjects) K8sObjects {
var ret K8sObjects
filterMap := make(map[*K8sObject]bool)
for _, list := range lists {
for _, object := range list {
filterMap[object] = true
}
}
for _, o := range objects {
if !filterMap[o] {
ret = append(ret, o)
}
}
return ret
}
// KindObjects returns the subset of objs with the given kind.
func KindObjects(objs K8sObjects, kind string) K8sObjects {
var ret K8sObjects
for _, o := range objs {
if o.Kind == kind {
ret = append(ret, o)
}
}
return ret
}
//// ParseK8SYAMLToIstioOperator parses a IstioOperator CustomResource YAML string and unmarshals in into
//// an IstioOperatorSpec object. It returns the object and an API group/version with it.
//func ParseK8SYAMLToIstioOperator(yml string) (*v1alpha1.HigressOperator, *schema.GroupVersionKind, error) {
// o, err := ParseYAMLToK8sObject([]byte(yml))
// if err != nil {
// return nil, nil, err
// }
// iop := &v1alpha1.HigressOperator{}
// if err := yaml.UnmarshalStrict([]byte(yml), iop); err != nil {
// return nil, nil, err
// }
// gvk := o.GroupVersionKind()
// //v1alpha1.SetNamespace(iop.Spec, o.Namespace)
// return iop, &gvk, nil
//}
// AllObjectHashes returns a map with object hashes of all the objects contained in cmm as the keys.
func AllObjectHashes(m string) map[string]bool {
ret := make(map[string]bool)
objs, err := ParseK8sObjectsFromYAMLManifest(m)
if err != nil {
}
for _, o := range objs {
ret[o.Hash()] = true
}
return ret
}
// resolvePDBConflict When user uses both minAvailable and
// maxUnavailable to configure istio instances, these two
// parameters are mutually exclusive, care must be taken
// to resolve the issue
func resolvePDBConflict(o *K8sObject) *K8sObject {
if o.json == nil {
return o
}
if o.object.Object["spec"] == nil {
return o
}
spec := o.object.Object["spec"].(map[string]any)
isDefault := func(item any) bool {
var ii intstr.IntOrString
switch item := item.(type) {
case int:
ii = intstr.FromInt(item)
case int64:
ii = intstr.FromInt(int(item))
case string:
ii = intstr.FromString(item)
default:
ii = intstr.FromInt(0)
}
intVal, err := intstr.GetScaledValueFromIntOrPercent(&ii, 100, false)
if err != nil || intVal == 0 {
return true
}
return false
}
if spec["maxUnavailable"] != nil && spec["minAvailable"] != nil {
// When both maxUnavailable and minAvailable present and
// neither has value 0, this is considered a conflict,
// then maxUnavailale will take precedence.
if !isDefault(spec["maxUnavailable"]) && !isDefault(spec["minAvailable"]) {
delete(spec, "minAvailable")
// Make sure that the json and yaml representation of the object
// is consistent with the changed object
o.json = nil
o.json, _ = o.JSON()
if o.yaml != nil {
o.yaml = nil
o.yaml, _ = o.YAML()
}
}
}
return o
}

View File

@@ -0,0 +1,713 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"strings"
"testing"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func TestHash(t *testing.T) {
hashTests := []struct {
desc string
kind string
namespace string
name string
want string
}{
{"CalculateHashForObjectWithNormalCharacter", "Service", "default", "ingressgateway", "Service:default:ingressgateway"},
{"CalculateHashForObjectWithDash", "Deployment", "istio-system", "istio-pilot", "Deployment:istio-system:istio-pilot"},
{"CalculateHashForObjectWithDot", "ConfigMap", "istio-system", "my.config", "ConfigMap:istio-system:my.config"},
}
for _, tt := range hashTests {
t.Run(tt.desc, func(t *testing.T) {
got := Hash(tt.kind, tt.namespace, tt.name)
if got != tt.want {
t.Errorf("Hash(%s): got %s for kind %s, namespace %s, name %s, want %s", tt.desc, got, tt.kind, tt.namespace, tt.name, tt.want)
}
})
}
}
func TestFromHash(t *testing.T) {
hashTests := []struct {
desc string
hash string
kind string
namespace string
name string
}{
{"ParseHashWithNormalCharacter", "Service:default:ingressgateway", "Service", "default", "ingressgateway"},
{"ParseHashForObjectWithDash", "Deployment:istio-system:istio-pilot", "Deployment", "istio-system", "istio-pilot"},
{"ParseHashForObjectWithDot", "ConfigMap:istio-system:my.config", "ConfigMap", "istio-system", "my.config"},
{"InvalidHash", "test", "Bad hash string: test", "", ""},
}
for _, tt := range hashTests {
t.Run(tt.desc, func(t *testing.T) {
k, ns, name := FromHash(tt.hash)
if k != tt.kind || ns != tt.namespace || name != tt.name {
t.Errorf("FromHash(%s): got kind %s, namespace %s, name %s, want kind %s, namespace %s, name %s", tt.desc, k, ns, name, tt.kind, tt.namespace, tt.name)
}
})
}
}
func TestHashNameKind(t *testing.T) {
hashNameKindTests := []struct {
desc string
kind string
name string
want string
}{
{"CalculateHashNameKindForObjectWithNormalCharacter", "Service", "ingressgateway", "Service:ingressgateway"},
{"CalculateHashNameKindForObjectWithDash", "Deployment", "istio-pilot", "Deployment:istio-pilot"},
{"CalculateHashNameKindForObjectWithDot", "ConfigMap", "my.config", "ConfigMap:my.config"},
}
for _, tt := range hashNameKindTests {
t.Run(tt.desc, func(t *testing.T) {
got := HashNameKind(tt.kind, tt.name)
if got != tt.want {
t.Errorf("HashNameKind(%s): got %s for kind %s, name %s, want %s", tt.desc, got, tt.kind, tt.name, tt.want)
}
})
}
}
func TestParseJSONToK8sObject(t *testing.T) {
testDeploymentJSON := `{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "istio-citadel",
"namespace": "istio-system",
"labels": {
"istio": "citadel"
}
},
"spec": {
"replicas": 1,
"selector": {
"matchLabels": {
"istio": "citadel"
}
},
"template": {
"metadata": {
"labels": {
"istio": "citadel"
}
},
"spec": {
"containers": [
{
"name": "citadel",
"image": "docker.io/istio/citadel:1.1.8",
"args": [
"--append-dns-names=true",
"--grpc-port=8060",
"--grpc-hostname=citadel",
"--citadel-storage-namespace=istio-system",
"--custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system",
"--monitoring-port=15014",
"--self-signed-ca=true"
]
}
]
}
}
}
}`
testPodJSON := `{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "istio-galley-75bcd59768-hpt5t",
"namespace": "istio-system",
"labels": {
"istio": "galley"
}
},
"spec": {
"containers": [
{
"name": "galley",
"image": "docker.io/istio/galley:1.1.8",
"command": [
"/usr/local/bin/galley",
"server",
"--meshConfigFile=/etc/mesh-config/mesh",
"--livenessProbeInterval=1s",
"--livenessProbePath=/healthliveness",
"--readinessProbePath=/healthready",
"--readinessProbeInterval=1s",
"--deployment-namespace=istio-system",
"--insecure=true",
"--validation-webhook-config-file",
"/etc/config/validatingwebhookconfiguration.yaml",
"--monitoringPort=15014",
"--log_output_level=default:info"
],
"ports": [
{
"containerPort": 443,
"protocol": "TCP"
},
{
"containerPort": 15014,
"protocol": "TCP"
},
{
"containerPort": 9901,
"protocol": "TCP"
}
]
}
]
}
}`
testServiceJSON := `{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"labels": {
"app": "pilot"
},
"name": "istio-pilot",
"namespace": "istio-system"
},
"spec": {
"clusterIP": "10.102.230.31",
"ports": [
{
"name": "grpc-xds",
"port": 15010,
"protocol": "TCP",
"targetPort": 15010
},
{
"name": "https-xds",
"port": 15011,
"protocol": "TCP",
"targetPort": 15011
},
{
"name": "http-legacy-discovery",
"port": 8080,
"protocol": "TCP",
"targetPort": 8080
},
{
"name": "http-monitoring",
"port": 15014,
"protocol": "TCP",
"targetPort": 15014
}
],
"selector": {
"istio": "pilot"
},
"sessionAffinity": "None",
"type": "ClusterIP"
}
}`
testInvalidJSON := `invalid json`
parseJSONToK8sObjectTests := []struct {
desc string
objString string
wantGroup string
wantKind string
wantName string
wantNamespace string
wantErr bool
}{
{"ParseJsonToK8sDeployment", testDeploymentJSON, "apps", "Deployment", "istio-citadel", "istio-system", false},
{"ParseJsonToK8sPod", testPodJSON, "", "Pod", "istio-galley-75bcd59768-hpt5t", "istio-system", false},
{"ParseJsonToK8sService", testServiceJSON, "", "Service", "istio-pilot", "istio-system", false},
{"ParseJsonError", testInvalidJSON, "", "", "", "", true},
}
for _, tt := range parseJSONToK8sObjectTests {
t.Run(tt.desc, func(t *testing.T) {
k8sObj, err := ParseJSONToK8sObject([]byte(tt.objString))
if err == nil {
if tt.wantErr {
t.Errorf("ParseJsonToK8sObject(%s): should be error", tt.desc)
}
k8sObjStr := k8sObj.YAMLDebugString()
if k8sObj.Group != tt.wantGroup {
t.Errorf("ParseJsonToK8sObject(%s): got group %s for k8s object %s, want %s", tt.desc, k8sObj.Group, k8sObjStr, tt.wantGroup)
}
if k8sObj.Kind != tt.wantKind {
t.Errorf("ParseJsonToK8sObject(%s): got kind %s for k8s object %s, want %s", tt.desc, k8sObj.Kind, k8sObjStr, tt.wantKind)
}
if k8sObj.Name != tt.wantName {
t.Errorf("ParseJsonToK8sObject(%s): got name %s for k8s object %s, want %s", tt.desc, k8sObj.Name, k8sObjStr, tt.wantName)
}
if k8sObj.Namespace != tt.wantNamespace {
t.Errorf("ParseJsonToK8sObject(%s): got group %s for k8s object %s, want %s", tt.desc, k8sObj.Namespace, k8sObjStr, tt.wantNamespace)
}
} else if !tt.wantErr {
t.Errorf("ParseJsonToK8sObject(%s): got unexpected error: %v", tt.desc, err)
}
})
}
}
func TestParseYAMLToK8sObject(t *testing.T) {
testDeploymentYaml := `apiVersion: apps/v1
kind: Deployment
metadata:
name: istio-citadel
namespace: istio-system
labels:
istio: citadel
spec:
replicas: 1
selector:
matchLabels:
istio: citadel
template:
metadata:
labels:
istio: citadel
spec:
containers:
- name: citadel
image: docker.io/istio/citadel:1.1.8
args:
- "--append-dns-names=true"
- "--grpc-port=8060"
- "--grpc-hostname=citadel"
- "--citadel-storage-namespace=istio-system"
- "--custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system"
- "--monitoring-port=15014"
- "--self-signed-ca=true"`
testPodYaml := `apiVersion: v1
kind: Pod
metadata:
name: istio-galley-75bcd59768-hpt5t
namespace: istio-system
labels:
istio: galley
spec:
containers:
- name: galley
image: docker.io/istio/galley:1.1.8
command:
- "/usr/local/bin/galley"
- server
- "--meshConfigFile=/etc/mesh-config/mesh"
- "--livenessProbeInterval=1s"
- "--livenessProbePath=/healthliveness"
- "--readinessProbePath=/healthready"
- "--readinessProbeInterval=1s"
- "--deployment-namespace=istio-system"
- "--insecure=true"
- "--validation-webhook-config-file"
- "/etc/config/validatingwebhookconfiguration.yaml"
- "--monitoringPort=15014"
- "--log_output_level=default:info"
ports:
- containerPort: 443
protocol: TCP
- containerPort: 15014
protocol: TCP
- containerPort: 9901
protocol: TCP`
testServiceYaml := `apiVersion: v1
kind: Service
metadata:
labels:
app: pilot
name: istio-pilot
namespace: istio-system
spec:
clusterIP: 10.102.230.31
ports:
- name: grpc-xds
port: 15010
protocol: TCP
targetPort: 15010
- name: https-xds
port: 15011
protocol: TCP
targetPort: 15011
- name: http-legacy-discovery
port: 8080
protocol: TCP
targetPort: 8080
- name: http-monitoring
port: 15014
protocol: TCP
targetPort: 15014
selector:
istio: pilot
sessionAffinity: None
type: ClusterIP`
parseYAMLToK8sObjectTests := []struct {
desc string
objString string
wantGroup string
wantKind string
wantName string
wantNamespace string
}{
{"ParseYamlToK8sDeployment", testDeploymentYaml, "apps", "Deployment", "istio-citadel", "istio-system"},
{"ParseYamlToK8sPod", testPodYaml, "", "Pod", "istio-galley-75bcd59768-hpt5t", "istio-system"},
{"ParseYamlToK8sService", testServiceYaml, "", "Service", "istio-pilot", "istio-system"},
}
for _, tt := range parseYAMLToK8sObjectTests {
t.Run(tt.desc, func(t *testing.T) {
k8sObj, err := ParseYAMLToK8sObject([]byte(tt.objString))
if err != nil {
k8sObjStr := k8sObj.YAMLDebugString()
if k8sObj.Group != tt.wantGroup {
t.Errorf("ParseYAMLToK8sObject(%s): got group %s for k8s object %s, want %s", tt.desc, k8sObj.Group, k8sObjStr, tt.wantGroup)
}
if k8sObj.Group != tt.wantGroup {
t.Errorf("ParseYAMLToK8sObject(%s): got kind %s for k8s object %s, want %s", tt.desc, k8sObj.Kind, k8sObjStr, tt.wantKind)
}
if k8sObj.Name != tt.wantName {
t.Errorf("ParseYAMLToK8sObject(%s): got name %s for k8s object %s, want %s", tt.desc, k8sObj.Name, k8sObjStr, tt.wantName)
}
if k8sObj.Namespace != tt.wantNamespace {
t.Errorf("ParseYAMLToK8sObject(%s): got group %s for k8s object %s, want %s", tt.desc, k8sObj.Namespace, k8sObjStr, tt.wantNamespace)
}
}
})
}
}
func TestParseK8sObjectsFromYAMLManifest(t *testing.T) {
testDeploymentYaml := `apiVersion: apps/v1
kind: Deployment
metadata:
name: istio-citadel
namespace: istio-system
labels:
istio: citadel
spec:
replicas: 1
selector:
matchLabels:
istio: citadel
template:
metadata:
labels:
istio: citadel
spec:
containers:
- name: citadel
image: docker.io/istio/citadel:1.1.8
args:
- "--append-dns-names=true"
- "--grpc-port=8060"
- "--grpc-hostname=citadel"
- "--citadel-storage-namespace=istio-system"
- "--custom-dns-names=istio-pilot-service-account.istio-system:istio-pilot.istio-system"
- "--monitoring-port=15014"
- "--self-signed-ca=true"`
testPodYaml := `apiVersion: v1
kind: Pod
metadata:
name: istio-galley-75bcd59768-hpt5t
namespace: istio-system
labels:
istio: galley
spec:
containers:
- name: galley
image: docker.io/istio/galley:1.1.8
command:
- "/usr/local/bin/galley"
- server
- "--meshConfigFile=/etc/mesh-config/mesh"
- "--livenessProbeInterval=1s"
- "--livenessProbePath=/healthliveness"
- "--readinessProbePath=/healthready"
- "--readinessProbeInterval=1s"
- "--deployment-namespace=istio-system"
- "--insecure=true"
- "--validation-webhook-config-file"
- "/etc/config/validatingwebhookconfiguration.yaml"
- "--monitoringPort=15014"
- "--log_output_level=default:info"
ports:
- containerPort: 443
protocol: TCP
- containerPort: 15014
protocol: TCP
- containerPort: 9901
protocol: TCP`
testServiceYaml := `apiVersion: v1
kind: Service
metadata:
labels:
app: pilot
name: istio-pilot
namespace: istio-system
spec:
clusterIP: 10.102.230.31
ports:
- name: grpc-xds
port: 15010
protocol: TCP
targetPort: 15010
- name: https-xds
port: 15011
protocol: TCP
targetPort: 15011
- name: http-legacy-discovery
port: 8080
protocol: TCP
targetPort: 8080
- name: http-monitoring
port: 15014
protocol: TCP
targetPort: 15014
selector:
istio: pilot
sessionAffinity: None
type: ClusterIP`
parseK8sObjectsFromYAMLManifestTests := []struct {
desc string
objsMap map[string]string
}{
{
"FromHybridYAMLManifest",
map[string]string{
"Deployment:istio-system:istio-citadel": testDeploymentYaml,
"Pod:istio-system:istio-galley-75bcd59768-hpt5t": testPodYaml,
"Service:istio-system:istio-pilot": testServiceYaml,
},
},
}
for _, tt := range parseK8sObjectsFromYAMLManifestTests {
t.Run(tt.desc, func(t *testing.T) {
testManifestYaml := strings.Join([]string{testDeploymentYaml, testPodYaml, testServiceYaml}, YAMLSeparator)
gotK8sObjs, err := ParseK8sObjectsFromYAMLManifest(testManifestYaml)
if err != nil {
gotK8sObjsMap := gotK8sObjs.ToMap()
for objHash, want := range tt.objsMap {
if gotObj, ok := gotK8sObjsMap[objHash]; ok {
gotObjYaml := gotObj.YAMLDebugString()
if !util.IsYAMLEqual(gotObjYaml, want) {
t.Errorf("ParseK8sObjectsFromYAMLManifest(%s): got:\n%s\n\nwant:\n%s\nDiff:\n%s\n", tt.desc, gotObjYaml, want, util.YAMLDiff(gotObjYaml, want))
}
}
}
}
})
}
}
func TestK8sObject_Equal(t *testing.T) {
obj1 := K8sObject{
object: &unstructured.Unstructured{Object: map[string]any{
"key": "value1",
}},
}
obj2 := K8sObject{
object: &unstructured.Unstructured{Object: map[string]any{
"key": "value2",
}},
}
cases := []struct {
desc string
o1 *K8sObject
o2 *K8sObject
want bool
}{
{
desc: "Equals",
o1: &obj1,
o2: &obj1,
want: true,
},
{
desc: "NotEquals",
o1: &obj1,
o2: &obj2,
want: false,
},
{
desc: "NilSource",
o1: nil,
o2: &obj2,
want: false,
},
{
desc: "NilDest",
o1: &obj1,
o2: nil,
want: false,
},
{
desc: "TwoNils",
o1: nil,
o2: nil,
want: true,
},
}
for _, tt := range cases {
t.Run(tt.desc, func(t *testing.T) {
res := tt.o1.Equal(tt.o2)
if res != tt.want {
t.Errorf("got %v, want: %v", res, tt.want)
}
})
}
}
func TestK8sObject_ResolveK8sConflict(t *testing.T) {
getK8sObject := func(ystr string) *K8sObject {
o, err := ParseYAMLToK8sObject([]byte(ystr))
if err != nil {
panic(err)
}
// Ensure that json data is in sync.
// Since the object was created using yaml, json is empty.
// make sure the object json is set correctly.
o.json, _ = o.JSON()
return o
}
cases := []struct {
desc string
o1 *K8sObject
o2 *K8sObject
}{
{
desc: "not applicable kind",
o1: getK8sObject(`
apiVersion: v1
kind: Service
metadata:
labels:
app: pilot
name: istio-pilot
namespace: istio-system
spec:
clusterIP: 10.102.230.31`),
o2: getK8sObject(`
apiVersion: v1
kind: Service
metadata:
labels:
app: pilot
name: istio-pilot
namespace: istio-system
spec:
clusterIP: 10.102.230.31`),
},
{
desc: "only minAvailable is set",
o1: getK8sObject(`
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 2`),
o2: getK8sObject(`
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 2`),
},
{
desc: "only maxUnavailable is set",
o1: getK8sObject(`
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: istio
spec:
maxUnavailable: 3`),
o2: getK8sObject(`
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: istio
spec:
maxUnavailable: 3`),
},
{
desc: "minAvailable and maxUnavailable are set to none zero values",
o1: getK8sObject(`
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: istio
spec:
maxUnavailable: 50%
minAvailable: 3`),
o2: getK8sObject(`
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: istio
spec:
maxUnavailable: 50%`),
},
{
desc: "both minAvailable and maxUnavailable are set default",
o1: getK8sObject(`
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: istio
spec:
minAvailable: 0
maxUnavailable: 0`),
o2: getK8sObject(`
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: istio
spec:
maxUnavailable: 0
minAvailable: 0`),
},
}
for _, tt := range cases {
t.Run(tt.desc, func(t *testing.T) {
newObj := tt.o1.ResolveK8sConflict()
if !newObj.Equal(tt.o2) {
newObjjson, _ := newObj.JSON()
wantedObjjson, _ := tt.o2.JSON()
t.Errorf("Got: %s, want: %s", string(newObjjson), string(wantedObjjson))
}
})
}
}

View File

@@ -0,0 +1,287 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helm
import (
"errors"
"fmt"
"sigs.k8s.io/yaml"
)
type InstallMode string
const (
InstallK8s InstallMode = "k8s"
InstallLocalK8s InstallMode = "local-k8s"
InstallLocalDocker InstallMode = "local-docker"
InstallLocal InstallMode = "local"
)
type Profile struct {
Profile string `json:"profile,omitempty"`
InstallPackagePath string `json:"installPackagePath,omitempty"`
Global ProfileGlobal `json:"global,omitempty"`
Console ProfileConsole `json:"console,omitempty"`
Gateway ProfileGateway `json:"gateway,omitempty"`
Controller ProfileController `json:"controller,omitempty"`
Storage ProfileStorage `json:"storage,omitempty"`
Values map[string]any `json:"values,omitempty"`
Charts ProfileCharts `json:"charts,omitempty"`
}
type ProfileGlobal struct {
Install InstallMode `json:"install,omitempty"`
IngressClass string `json:"ingressClass,omitempty"`
WatchNamespace string `json:"watchNamespace,omitempty"`
DisableAlpnH2 bool `json:"disableAlpnH2,omitempty"`
EnableStatus bool `json:"enableStatus,omitempty"`
EnableIstioAPI bool `json:"enableIstioAPI,omitempty"`
Namespace string `json:"namespace,omitempty"`
IstioNamespace string `json:"istioNamespace,omitempty"`
}
func (p ProfileGlobal) SetFlags(install InstallMode) ([]string, error) {
sets := make([]string, 0)
sets = append(sets, fmt.Sprintf("global.ingressClass=%s", p.IngressClass))
sets = append(sets, fmt.Sprintf("global.watchNamespace=%s", p.WatchNamespace))
sets = append(sets, fmt.Sprintf("global.disableAlpnH2=%t", p.DisableAlpnH2))
sets = append(sets, fmt.Sprintf("global.enableStatus=%t", p.EnableStatus))
sets = append(sets, fmt.Sprintf("global.enableIstioAPI=%t", p.EnableIstioAPI))
sets = append(sets, fmt.Sprintf("global.istioNamespace=%s", p.IstioNamespace))
if install == InstallLocalK8s {
sets = append(sets, fmt.Sprintf("global.local=%t", true))
}
return sets, nil
}
func (p ProfileGlobal) Validate(install InstallMode) []error {
errs := make([]error, 0)
// now only support k8s and local-k8s installation mode
if p.Install != InstallK8s && p.Install != InstallLocalK8s {
errs = append(errs, errors.New("global.install only can be set to k8s or local-k8s"))
}
if len(p.IngressClass) == 0 {
errs = append(errs, errors.New("global.ingressClass can't be empty"))
}
if len(p.Namespace) == 0 {
errs = append(errs, errors.New("global.namespace can't be empty"))
}
if len(p.IstioNamespace) == 0 {
errs = append(errs, errors.New("global.istioNamespace can't be empty"))
}
return errs
}
type ProfileConsole struct {
Port uint32 `json:"port,omitempty"`
Replicas uint32 `json:"replicas,omitempty"`
ServiceType string `json:"serviceType,omitempty"`
Domain string `json:"domain,omitempty"`
TlsSecretName string `json:"tlsSecretName,omitempty"`
WebLoginPrompt string `json:"webLoginPrompt,omitempty"`
AdminPasswordValue string `json:"adminPasswordValue,omitempty"`
AdminPasswordLength uint32 `json:"adminPasswordLength,omitempty"`
O11yEnabled bool `json:"o11YEnabled,omitempty"`
PvcRwxSupported bool `json:"pvcRwxSupported,omitempty"`
}
func (p ProfileConsole) SetFlags(install InstallMode) ([]string, error) {
sets := make([]string, 0)
sets = append(sets, fmt.Sprintf("higress-console.replicaCount=%d", p.Replicas))
sets = append(sets, fmt.Sprintf("higress-console.service.type=%s", p.ServiceType))
sets = append(sets, fmt.Sprintf("higress-console.domain=%s", p.Domain))
sets = append(sets, fmt.Sprintf("higress-console.tlsSecretName=%s", p.TlsSecretName))
sets = append(sets, fmt.Sprintf("higress-console.web.login.prompt=%s", p.WebLoginPrompt))
sets = append(sets, fmt.Sprintf("higress-console.admin.password.value=%s", p.AdminPasswordValue))
sets = append(sets, fmt.Sprintf("higress-console.admin.password.length=%d", p.AdminPasswordLength))
sets = append(sets, fmt.Sprintf("higress-console.o11y.enabled=%t", p.O11yEnabled))
sets = append(sets, fmt.Sprintf("higress-console.pvc.rwxSupported=%t", p.PvcRwxSupported))
return sets, nil
}
func (p ProfileConsole) Validate(install InstallMode) []error {
errs := make([]error, 0)
if p.Replicas <= 0 {
errs = append(errs, errors.New("console.replica need be large than zero"))
}
if p.ServiceType != "ClusterIP" && p.ServiceType != "NodePort" && p.ServiceType != "LoadBalancer" {
errs = append(errs, errors.New("console.serviceType can only be set to ClusterIP, NodePort or LoadBalancer"))
}
return errs
}
type ProfileGateway struct {
Replicas uint32 `json:"replicas,omitempty"`
HttpPort uint32 `json:"httpPort,omitempty"`
HttpsPort uint32 `json:"httpsPort,omitempty"`
MetricsPort uint32 `json:"metricsPort,omitempty"`
}
func (p ProfileGateway) SetFlags(install InstallMode) ([]string, error) {
sets := make([]string, 0)
sets = append(sets, fmt.Sprintf("higress-core.gateway.replicas=%d", p.Replicas))
return sets, nil
}
func (p ProfileGateway) Validate(install InstallMode) []error {
errs := make([]error, 0)
if p.Replicas <= 0 {
errs = append(errs, errors.New("gateway.replica need be large than zero"))
}
return errs
}
type ProfileController struct {
Replicas uint32 `json:"replicas,omitempty"`
}
func (p ProfileController) SetFlags(install InstallMode) ([]string, error) {
sets := make([]string, 0)
sets = append(sets, fmt.Sprintf("higress-core.controller.replicas=%d", p.Replicas))
return sets, nil
}
func (p ProfileController) Validate(install InstallMode) []error {
errs := make([]error, 0)
if p.Replicas <= 0 {
errs = append(errs, errors.New("controller.replica need be large than zero"))
}
return errs
}
type ProfileStorage struct {
Url string `json:"url,omitempty"`
Ns string `json:"ns,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
DataEncKey string `json:"DataEncKey,omitempty"`
}
func (p ProfileStorage) Validate(install InstallMode) []error {
errs := make([]error, 0)
return errs
}
type Chart struct {
Url string `json:"url,omitempty"`
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
}
type ProfileCharts struct {
Higress Chart `json:"higress,omitempty"`
Istio Chart `json:"istio,omitempty"`
}
func (p ProfileCharts) Validate(install InstallMode) []error {
errs := make([]error, 0)
return errs
}
func (p *Profile) ValuesYaml() (string, error) {
setFlags := make([]string, 0)
// Get global setting
globalFlags, _ := p.Global.SetFlags(p.Global.Install)
setFlags = append(setFlags, globalFlags...)
// Get console setting
consoleFlags, _ := p.Console.SetFlags(p.Global.Install)
setFlags = append(setFlags, consoleFlags...)
// Get gateway setting
gatewayFlags, _ := p.Gateway.SetFlags(p.Global.Install)
setFlags = append(setFlags, gatewayFlags...)
// Get controller setting
controllerFlags, _ := p.Controller.SetFlags(p.Global.Install)
setFlags = append(setFlags, controllerFlags...)
valueOverlayYAML := ""
if p.Values != nil {
out, err := yaml.Marshal(p.Values)
if err != nil {
return "", err
}
valueOverlayYAML = string(out)
}
// merge values and setFlags
overlayYAML, err := overlaySetFlagValues(valueOverlayYAML, setFlags)
if err != nil {
return "", err
}
return overlayYAML, nil
}
func (p *Profile) IstioEnabled() bool {
if (p.Global.Install == InstallK8s || p.Global.Install == InstallLocalK8s) && p.Global.EnableIstioAPI {
return true
}
return false
}
func (p *Profile) Validate() error {
errs := make([]error, 0)
errsGlobal := p.Global.Validate(p.Global.Install)
if len(errsGlobal) > 0 {
errs = append(errs, errsGlobal...)
}
errsConsole := p.Console.Validate(p.Global.Install)
if len(errsConsole) > 0 {
errs = append(errs, errsConsole...)
}
errsGateway := p.Gateway.Validate(p.Global.Install)
if len(errsGateway) > 0 {
errs = append(errs, errsGateway...)
}
errsController := p.Controller.Validate(p.Global.Install)
if len(errsController) > 0 {
errs = append(errs, errsController...)
}
errsStorage := p.Storage.Validate(p.Global.Install)
if len(errsController) > 0 {
errs = append(errs, errsStorage...)
}
errsCharts := p.Charts.Validate(p.Global.Install)
if len(errsCharts) > 0 {
errs = append(errs, errsCharts...)
}
if len(errs) == 0 {
return nil
}
return errors.New(ToString(errs, "\n"))
}
// ToString returns a string representation of errors, with elements separated by separator string. Any nil errors in the
// slice are skipped.
func ToString(errors []error, separator string) string {
var out string
for i, e := range errors {
if e == nil {
continue
}
if i != 0 {
out += separator
}
out += e.Error()
}
return out
}

View File

@@ -0,0 +1,593 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package helm
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"net/url"
"os"
"path"
"path/filepath"
"sort"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/manifests"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/downloader"
"helm.sh/helm/v3/pkg/engine"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/repo"
"sigs.k8s.io/yaml"
)
const (
// DefaultProfileName is the name of the default profile for installation.
DefaultProfileName = "local-k8s"
// DefaultProfileFilename is the name of the default profile yaml file for installation.
DefaultProfileFilename = "local-k8s.yaml"
// DefaultUninstallProfileName is the name of the default profile yaml file for uninstallation.
DefaultUninstallProfileName = "local-k8s"
// ChartsSubdirName = "charts"
profilesRoot = "profiles"
RepoLatestVersion = "latest"
RepoChartIndexYamlHigressIndex = "higress"
YAMLSeparator = "\n---\n"
NotesFileNameSuffix = ".txt"
)
func LoadValues(profileName string, chartsDir string) (string, error) {
path := strings.Join([]string{profilesRoot, builtinProfileToFilename(profileName)}, "/")
by, err := fs.ReadFile(manifests.BuiltinOrDir(chartsDir), path)
if err != nil {
return "", err
}
return string(by), nil
}
func readProfiles(chartsDir string) (map[string]bool, error) {
profiles := map[string]bool{}
f := manifests.BuiltinOrDir(chartsDir)
dir, err := fs.ReadDir(f, profilesRoot)
if err != nil {
return nil, err
}
for _, f := range dir {
if f.Name() == "_all.yaml" {
continue
}
trimmedString := strings.TrimSuffix(f.Name(), ".yaml")
if f.Name() != trimmedString {
profiles[trimmedString] = true
}
}
return profiles, nil
}
func builtinProfileToFilename(name string) string {
if name == "" {
return DefaultProfileFilename
}
return name + ".yaml"
}
// stripPrefix removes the given prefix from prefix.
func stripPrefix(path, prefix string) string {
pl := len(strings.Split(prefix, "/"))
pv := strings.Split(path, "/")
return strings.Join(pv[pl:], "/")
}
// ListProfiles list all the profiles.
func ListProfiles(charts string) ([]string, error) {
profiles, err := readProfiles(charts)
if err != nil {
return nil, err
}
return util.StringBoolMapToSlice(profiles), nil
}
var DefaultFilters = []util.FilterFunc{
util.LicenseFilter,
util.FormatterFilter,
util.SpaceFilter,
}
// Renderer is responsible for rendering helm chart with new values.
type Renderer interface {
Init() error
RenderManifest(valsYaml string) (string, error)
SetVersion(version string)
}
type RendererOptions struct {
Name string
Namespace string
// fields for LocalRenderer
FS fs.FS
Dir string
// fields for RemoteRenderer
Version string
RepoURL string
}
type RendererOption func(*RendererOptions)
func WithName(name string) RendererOption {
return func(opts *RendererOptions) {
opts.Name = name
}
}
func WithNamespace(ns string) RendererOption {
return func(opts *RendererOptions) {
opts.Namespace = ns
}
}
func WithFS(f fs.FS) RendererOption {
return func(opts *RendererOptions) {
opts.FS = f
}
}
func WithDir(dir string) RendererOption {
return func(opts *RendererOptions) {
opts.Dir = dir
}
}
func WithVersion(version string) RendererOption {
return func(opts *RendererOptions) {
opts.Version = version
}
}
func WithRepoURL(repo string) RendererOption {
return func(opts *RendererOptions) {
opts.RepoURL = repo
}
}
// LocalRenderer load chart from local file system
type LocalRenderer struct {
Opts *RendererOptions
Chart *chart.Chart
Started bool
}
func (lr *LocalRenderer) Init() error {
fileNames, err := getFileNames(lr.Opts.FS, lr.Opts.Dir)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("chart of component %s doesn't exist", lr.Opts.Name)
}
return fmt.Errorf("getFileNames err: %s", err)
}
var files []*loader.BufferedFile
for _, fileName := range fileNames {
data, err := fs.ReadFile(lr.Opts.FS, fileName)
if err != nil {
return fmt.Errorf("ReadFile %s err: %s", fileName, err)
}
// todo:// explain why we need to do this
name := util.StripPrefix(fileName, lr.Opts.Dir)
file := &loader.BufferedFile{
Name: name,
Data: data,
}
files = append(files, file)
}
newChart, err := loader.LoadFiles(files)
if err != nil {
return fmt.Errorf("load chart of component %s err: %s", lr.Opts.Name, err)
}
lr.Chart = newChart
lr.Started = true
return nil
}
func (lr *LocalRenderer) RenderManifest(valsYaml string) (string, error) {
if !lr.Started {
return "", errors.New("LocalRenderer has not been init")
}
return renderManifest(valsYaml, lr.Chart, true, lr.Opts, DefaultFilters...)
}
func (lr *LocalRenderer) SetVersion(version string) {
lr.Opts.Version = version
}
func NewLocalRenderer(opts ...RendererOption) (Renderer, error) {
newOpts := &RendererOptions{}
for _, opt := range opts {
opt(newOpts)
}
if err := verifyRendererOptions(newOpts); err != nil {
return nil, fmt.Errorf("verify err: %s", err)
}
return &LocalRenderer{
Opts: newOpts,
}, nil
}
type RemoteRenderer struct {
Opts *RendererOptions
Chart *chart.Chart
Started bool
}
func (rr *RemoteRenderer) initChartPathOptions() *action.ChartPathOptions {
return &action.ChartPathOptions{
RepoURL: rr.Opts.RepoURL,
Version: rr.Opts.Version,
}
}
func (rr *RemoteRenderer) Init() error {
cpOpts := rr.initChartPathOptions()
settings := cli.New()
// using release name as chart name by default
cp, err := locateChart(cpOpts, rr.Opts.Name, settings)
if err != nil {
return err
}
// Check chart dependencies to make sure all are present in /charts
chartRequested, err := loader.Load(cp)
if err != nil {
return err
}
if err := verifyInstallable(chartRequested); err != nil {
return err
}
rr.Chart = chartRequested
rr.Started = true
return nil
}
func (rr *RemoteRenderer) SetVersion(version string) {
rr.Opts.Version = version
}
func (rr *RemoteRenderer) RenderManifest(valsYaml string) (string, error) {
if !rr.Started {
return "", errors.New("RemoteRenderer has not been init")
}
return renderManifest(valsYaml, rr.Chart, false, rr.Opts, DefaultFilters...)
}
func NewRemoteRenderer(opts ...RendererOption) (Renderer, error) {
newOpts := &RendererOptions{}
for _, opt := range opts {
opt(newOpts)
}
return &RemoteRenderer{
Opts: newOpts,
}, nil
}
func verifyRendererOptions(opts *RendererOptions) error {
if opts.Name == "" {
return errors.New("missing component name for Renderer")
}
if opts.Namespace == "" {
return errors.New("missing component namespace for Renderer")
}
if opts.FS == nil {
return errors.New("missing chart FS for Renderer")
}
if opts.Dir == "" {
return errors.New("missing chart dir for Renderer")
}
return nil
}
// read all files recursively under root path from a certain local file system
func getFileNames(f fs.FS, root string) ([]string, error) {
var fileNames []string
if err := fs.WalkDir(f, root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
fileNames = append(fileNames, path)
return nil
}); err != nil {
return nil, err
}
return fileNames, nil
}
func verifyInstallable(cht *chart.Chart) error {
typ := cht.Metadata.Type
if typ == "" || typ == "application" {
return nil
}
return fmt.Errorf("%s chart %s is not installable", typ, cht.Name())
}
func renderManifest(valsYaml string, cht *chart.Chart, builtIn bool, opts *RendererOptions, filters ...util.FilterFunc) (string, error) {
valsMap := make(map[string]any)
if err := yaml.Unmarshal([]byte(valsYaml), &valsMap); err != nil {
return "", fmt.Errorf("unmarshal failed err: %s", err)
}
RelOpts := chartutil.ReleaseOptions{
Name: opts.Name,
Namespace: opts.Namespace,
}
// TODO need to specify k8s version
caps := chartutil.DefaultCapabilities
// maybe we need a configuration to change this caps
resVals, err := chartutil.ToRenderValues(cht, valsMap, RelOpts, caps)
if err != nil {
return "", fmt.Errorf("ToRenderValues failed err: %s", err)
}
if builtIn {
resVals["Values"].(chartutil.Values)["enabled"] = true
}
filesMap, err := engine.Render(cht, resVals)
if err != nil {
return "", fmt.Errorf("Render chart failed err: %s", err)
}
keys := make([]string, 0, len(filesMap))
for key := range filesMap {
// remove notation files such as Notes.txt
if strings.HasSuffix(key, NotesFileNameSuffix) {
continue
}
keys = append(keys, key)
}
// to ensure that every manifest rendered by same values are the same
sort.Strings(keys)
var builder strings.Builder
for i := 0; i < len(keys); i++ {
file := filesMap[keys[i]]
file = util.ApplyFilters(file, filters...)
// ignore empty manifest
if file == "" {
continue
}
if !strings.HasSuffix(file, YAMLSeparator) {
file += YAMLSeparator
}
builder.WriteString(file)
}
// render CRD
crdFiles := cht.CRDObjects()
// Sort crd files by name to ensure stable manifest output
sort.Slice(crdFiles, func(i, j int) bool { return crdFiles[i].Name < crdFiles[j].Name })
for _, crdFile := range crdFiles {
f := string(crdFile.File.Data)
// add yaml separator if the rendered file doesn't have one at the end
f = strings.TrimSpace(f) + "\n"
if !strings.HasSuffix(f, YAMLSeparator) {
f += YAMLSeparator
}
builder.WriteString(f)
}
return builder.String(), nil
}
// locateChart locate the target chart path by sequential orders:
// 1. find local helm repository using "name-version.tgz" format
// 2. using downloader to pull remote chart
func locateChart(cpOpts *action.ChartPathOptions, name string, settings *cli.EnvSettings) (string, error) {
name = strings.TrimSpace(name)
version := strings.TrimSpace(cpOpts.Version)
// check if it's in Helm's chart cache
// cacheName is hardcoded as format of helm. eg: grafana-6.31.1.tgz
cacheName := name + "-" + cpOpts.Version + ".tgz"
cachePath := path.Join(settings.RepositoryCache, cacheName)
if _, err := os.Stat(cachePath); err == nil {
abs, err := filepath.Abs(cachePath)
if err != nil {
return abs, err
}
if cpOpts.Verify {
if _, err := downloader.VerifyChart(abs, cpOpts.Keyring); err != nil {
return "", err
}
}
return abs, nil
}
dl := downloader.ChartDownloader{
Out: os.Stdout,
Keyring: cpOpts.Keyring,
Getters: getter.All(settings),
Options: []getter.Option{
getter.WithPassCredentialsAll(cpOpts.PassCredentialsAll),
getter.WithTLSClientConfig(cpOpts.CertFile, cpOpts.KeyFile, cpOpts.CaFile),
getter.WithInsecureSkipVerifyTLS(cpOpts.InsecureSkipTLSverify),
},
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
}
if cpOpts.Verify {
dl.Verify = downloader.VerifyAlways
}
if cpOpts.RepoURL != "" {
chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(cpOpts.RepoURL, cpOpts.Username, cpOpts.Password, name, version,
cpOpts.CertFile, cpOpts.KeyFile, cpOpts.CaFile, cpOpts.InsecureSkipTLSverify, cpOpts.PassCredentialsAll, getter.All(settings))
if err != nil {
return "", err
}
name = chartURL
// Only pass the user/pass on when the user has said to or when the
// location of the chart repo and the chart are the same domain.
u1, err := url.Parse(cpOpts.RepoURL)
if err != nil {
return "", err
}
u2, err := url.Parse(chartURL)
if err != nil {
return "", err
}
// Host on URL (returned from url.Parse) contains the port if present.
// This check ensures credentials are not passed between different
// services on different ports.
if cpOpts.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) {
dl.Options = append(dl.Options, getter.WithBasicAuth(cpOpts.Username, cpOpts.Password))
} else {
dl.Options = append(dl.Options, getter.WithBasicAuth("", ""))
}
} else {
dl.Options = append(dl.Options, getter.WithBasicAuth(cpOpts.Username, cpOpts.Password))
}
// if RepositoryCache doesn't exist, create it
if err := os.MkdirAll(settings.RepositoryCache, 0o755); err != nil {
return "", err
}
filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache)
if err != nil {
return "", err
}
fileAbsPath, err := filepath.Abs(filename)
if err != nil {
return filename, err
}
return fileAbsPath, nil
}
func ParseLatestVersion(repoUrl string, version string) (string, error) {
cpOpts := &action.ChartPathOptions{
RepoURL: repoUrl,
Version: version,
}
settings := cli.New()
indexURL, err := repo.ResolveReferenceURL(repoUrl, "index.yaml")
if err != nil {
return "", err
}
u, err := url.Parse(repoUrl)
if err != nil {
return "", fmt.Errorf("invalid chart URL format: %s", repoUrl)
}
client, err := getter.All(settings).ByScheme(u.Scheme)
if err != nil {
return "", fmt.Errorf("could not find protocol handler for: %s", u.Scheme)
}
resp, err := client.Get(indexURL,
getter.WithURL(cpOpts.RepoURL),
getter.WithInsecureSkipVerifyTLS(cpOpts.InsecureSkipTLSverify),
getter.WithTLSClientConfig(cpOpts.CertFile, cpOpts.KeyFile, cpOpts.CaFile),
getter.WithBasicAuth(cpOpts.Username, cpOpts.Password),
getter.WithPassCredentialsAll(cpOpts.PassCredentialsAll),
)
if err != nil {
return "", err
}
index, err := io.ReadAll(resp)
if err != nil {
return "", err
}
indexFile, err := loadIndex(index)
if err != nil {
return "", err
}
// get higress helm chart latest version
if entries, ok := indexFile.Entries[RepoChartIndexYamlHigressIndex]; ok {
return entries[0].AppVersion, nil
}
return "", errors.New("can't find higress latest version")
}
// loadIndex loads an index file and does minimal validity checking.
//
// The source parameter is only used for logging.
// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
func loadIndex(data []byte) (*repo.IndexFile, error) {
i := &repo.IndexFile{}
if len(data) == 0 {
return i, errors.New("empty index.yaml file")
}
if err := jsonOrYamlUnmarshal(data, i); err != nil {
return i, err
}
for _, cvs := range i.Entries {
for idx := len(cvs) - 1; idx >= 0; idx-- {
if cvs[idx] == nil {
continue
}
if cvs[idx].APIVersion == "" {
cvs[idx].APIVersion = chart.APIVersionV1
}
if err := cvs[idx].Validate(); err != nil {
cvs = append(cvs[:idx], cvs[idx+1:]...)
}
}
}
i.SortEntries()
if i.APIVersion == "" {
return i, errors.New("no API version specified")
}
return i, nil
}
// jsonOrYamlUnmarshal unmarshals the given byte slice containing JSON or YAML
// into the provided interface.
//
// It automatically detects whether the data is in JSON or YAML format by
// checking its validity as JSON. If the data is valid JSON, it will use the
// `encoding/json` package to unmarshal it. Otherwise, it will use the
// `sigs.k8s.io/yaml` package to unmarshal the YAML data.
func jsonOrYamlUnmarshal(b []byte, i interface{}) error {
if json.Valid(b) {
return json.Unmarshal(b, i)
}
return yaml.UnmarshalStrict(b, i)
}

View File

@@ -0,0 +1,548 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tpath
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"gopkg.in/yaml.v2"
yaml2 "sigs.k8s.io/yaml"
)
// PathContext provides a means for traversing a tree towards the root.
type PathContext struct {
// Parent in the Parent of this PathContext.
Parent *PathContext
// KeyToChild is the key required to reach the child.
KeyToChild any
// Node is the actual Node in the data tree.
Node any
}
// String implements the Stringer interface.
func (nc *PathContext) String() string {
ret := "\n--------------- NodeContext ------------------\n"
if nc.Parent != nil {
ret += fmt.Sprintf("Parent.Node=\n%s\n", nc.Parent.Node)
ret += fmt.Sprintf("KeyToChild=%v\n", nc.Parent.KeyToChild)
}
ret += fmt.Sprintf("Node=\n%s\n", nc.Node)
ret += "----------------------------------------------\n"
return ret
}
// GetPathContext returns the PathContext for the Node which has the given path from root.
// It returns false and no error if the given path is not found, or an error code in other error situations, like
// a malformed path.
// It also creates a tree of PathContexts during the traversal so that Parent nodes can be updated if required. This is
// required when (say) appending to a list, where the parent list itself must be updated.
func GetPathContext(root any, path util.Path, createMissing bool) (*PathContext, bool, error) {
return getPathContext(&PathContext{Node: root}, path, path, createMissing)
}
// WritePathContext writes the given value to the Node in the given PathContext.
func WritePathContext(nc *PathContext, value any, merge bool) error {
if !util.IsValueNil(value) {
return setPathContext(nc, value, merge)
}
if nc.Parent == nil {
return errors.New("cannot delete root element")
}
switch {
case isSliceOrPtrInterface(nc.Parent.Node):
if err := util.DeleteFromSlicePtr(nc.Parent.Node, nc.Parent.KeyToChild.(int)); err != nil {
return err
}
if isMapOrInterface(nc.Parent.Parent.Node) {
return util.InsertIntoMap(nc.Parent.Parent.Node, nc.Parent.Parent.KeyToChild, nc.Parent.Node)
}
// TODO: The case of deleting a list.list.node element is not currently supported.
return fmt.Errorf("cannot delete path: unsupported parent.parent type %T for delete", nc.Parent.Parent.Node)
case util.IsMap(nc.Parent.Node):
return util.DeleteFromMap(nc.Parent.Node, nc.Parent.KeyToChild)
default:
}
return fmt.Errorf("cannot delete path: unsupported parent type %T for delete", nc.Parent.Node)
}
// WriteNode writes value to the tree in root at the given path, creating any required missing internal nodes in path.
func WriteNode(root any, path util.Path, value any) error {
pc, _, err := getPathContext(&PathContext{Node: root}, path, path, true)
if err != nil {
return err
}
return WritePathContext(pc, value, false)
}
// MergeNode merges value to the tree in root at the given path, creating any required missing internal nodes in path.
func MergeNode(root any, path util.Path, value any) error {
pc, _, err := getPathContext(&PathContext{Node: root}, path, path, true)
if err != nil {
return err
}
return WritePathContext(pc, value, true)
}
// Find returns the value at path from the given tree, or false if the path does not exist.
// It behaves differently from GetPathContext in that it never creates map entries at the leaf and does not provide
// a way to mutate the parent of the found node.
func Find(inputTree map[string]any, path util.Path) (any, bool, error) {
if len(path) == 0 {
return nil, false, fmt.Errorf("path is empty")
}
node, found := find(inputTree, path)
return node, found, nil
}
// Delete sets value at path of input untyped tree to nil
func Delete(root map[string]any, path util.Path) (bool, error) {
pc, _, err := getPathContext(&PathContext{Node: root}, path, path, false)
if err != nil {
return false, err
}
return true, WritePathContext(pc, nil, false)
}
// getPathContext is the internal implementation of GetPathContext.
// If createMissing is true, it creates any missing map (but NOT list) path entries in root.
func getPathContext(nc *PathContext, fullPath, remainPath util.Path, createMissing bool) (*PathContext, bool, error) {
if len(remainPath) == 0 {
return nc, true, nil
}
pe := remainPath[0]
if nc.Node == nil {
if !createMissing {
return nil, false, fmt.Errorf("node %s is zero", pe)
}
if util.IsNPathElement(pe) || util.IsKVPathElement(pe) {
nc.Node = []any{}
} else {
nc.Node = make(map[string]any)
}
}
v := reflect.ValueOf(nc.Node)
if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
v = v.Elem()
}
ncNode := v.Interface()
// For list types, we need a key to identify the selected list item. This can be either a value key of the
// form :matching_value in the case of a leaf list, or a matching key:value in the case of a non-leaf list.
if lst, ok := ncNode.([]any); ok {
// If the path element has the form [N], a list element is being selected by index. Return the element at index
// N if it exists.
if util.IsNPathElement(pe) {
idx, err := util.PathN(pe)
if err != nil {
return nil, false, fmt.Errorf("path %s, index %s: %s", fullPath, pe, err)
}
var foundNode any
if idx >= len(lst) || idx < 0 {
if !createMissing {
return nil, false, fmt.Errorf("index %d exceeds list length %d at path %s", idx, len(lst), remainPath)
}
idx = len(lst)
foundNode = make(map[string]any)
} else {
foundNode = lst[idx]
}
nn := &PathContext{
Parent: nc,
Node: foundNode,
}
nc.KeyToChild = idx
return getPathContext(nn, fullPath, remainPath[1:], createMissing)
}
// Otherwise the path element must have form [key:value]. In this case, go through all list elements, which
// must have map type, and try to find one which has a matching key:value.
for idx, le := range lst {
// non-leaf list, expect to match item by key:value.
if lm, ok := le.(map[any]any); ok {
k, v, err := util.PathKV(pe)
if err != nil {
return nil, false, fmt.Errorf("path %s: %s", fullPath, err)
}
if stringsEqual(lm[k], v) {
nn := &PathContext{
Parent: nc,
Node: lm,
}
nc.KeyToChild = idx
nn.KeyToChild = k
if len(remainPath) == 1 {
return nn, true, nil
}
return getPathContext(nn, fullPath, remainPath[1:], createMissing)
}
continue
}
// repeat of the block above for the case where tree unmarshals to map[string]interface{}. There doesn't
// seem to be a way to merge this case into the above block.
if lm, ok := le.(map[string]any); ok {
k, v, err := util.PathKV(pe)
if err != nil {
return nil, false, fmt.Errorf("path %s: %s", fullPath, err)
}
if stringsEqual(lm[k], v) {
nn := &PathContext{
Parent: nc,
Node: lm,
}
nc.KeyToChild = idx
nn.KeyToChild = k
if len(remainPath) == 1 {
return nn, true, nil
}
return getPathContext(nn, fullPath, remainPath[1:], createMissing)
}
continue
}
// leaf list, expect path element [V], match based on value V.
v, err := util.PathV(pe)
if err != nil {
return nil, false, fmt.Errorf("path %s: %s", fullPath, err)
}
if matchesRegex(v, le) {
nn := &PathContext{
Parent: nc,
Node: le,
}
nc.KeyToChild = idx
return getPathContext(nn, fullPath, remainPath[1:], createMissing)
}
}
return nil, false, fmt.Errorf("path %s: element %s not found", fullPath, pe)
}
if util.IsMap(ncNode) {
var nn any
if m, ok := ncNode.(map[any]any); ok {
nn, ok = m[pe]
if !ok {
// remainPath == 1 means the patch is creation of a new leaf.
if createMissing || len(remainPath) == 1 {
m[pe] = make(map[any]any)
nn = m[pe]
} else {
return nil, false, fmt.Errorf("path not found at element %s in path %s", pe, fullPath)
}
}
}
if reflect.ValueOf(ncNode).IsNil() {
ncNode = make(map[string]any)
nc.Node = ncNode
}
if m, ok := ncNode.(map[string]any); ok {
nn, ok = m[pe]
if !ok {
// remainPath == 1 means the patch is creation of a new leaf.
if createMissing || len(remainPath) == 1 {
nextElementNPath := len(remainPath) > 1 && util.IsNPathElement(remainPath[1])
if nextElementNPath {
m[pe] = make([]any, 0)
} else {
m[pe] = make(map[string]any)
}
nn = m[pe]
} else {
return nil, false, fmt.Errorf("path not found at element %s in path %s", pe, fullPath)
}
}
}
npc := &PathContext{
Parent: nc,
Node: nn,
}
// for slices, use the address so that the slice can be mutated.
if util.IsSlice(nn) {
npc.Node = &nn
}
nc.KeyToChild = pe
return getPathContext(npc, fullPath, remainPath[1:], createMissing)
}
return nil, false, fmt.Errorf("leaf type %T in non-leaf Node %s", nc.Node, remainPath)
}
// setPathContext writes the given value to the Node in the given PathContext,
// enlarging all PathContext lists to ensure all indexes are valid.
func setPathContext(nc *PathContext, value any, merge bool) error {
processParent, err := setValueContext(nc, value, merge)
if err != nil || !processParent {
return err
}
// If the path included insertions, process them now
if nc.Parent.Parent == nil {
return nil
}
return setPathContext(nc.Parent, nc.Parent.Node, false) // note: tail recursive
}
// setValueContext writes the given value to the Node in the given PathContext.
// If setting the value requires growing the final slice, grows it.
func setValueContext(nc *PathContext, value any, merge bool) (bool, error) {
if nc.Parent == nil {
return false, nil
}
vv, mapFromString := tryToUnmarshalStringToYAML(value)
switch parentNode := nc.Parent.Node.(type) {
case *any:
switch vParentNode := (*parentNode).(type) {
case []any:
idx := nc.Parent.KeyToChild.(int)
if idx == -1 {
// Treat -1 as insert-at-end of list
idx = len(vParentNode)
}
if idx >= len(vParentNode) {
newElements := make([]any, idx-len(vParentNode)+1)
vParentNode = append(vParentNode, newElements...)
*parentNode = vParentNode
}
merged, err := mergeConditional(vv, nc.Node, merge)
if err != nil {
return false, err
}
vParentNode[idx] = merged
nc.Node = merged
default:
return false, fmt.Errorf("don't know about vtype %T", vParentNode)
}
case map[string]any:
key := nc.Parent.KeyToChild.(string)
// Update is treated differently depending on whether the value is a scalar or map type. If scalar,
// insert a new element into the terminal node, otherwise replace the terminal node with the new subtree.
if ncNode, ok := nc.Node.(*any); ok && !mapFromString {
switch vNcNode := (*ncNode).(type) {
case []any:
switch vv.(type) {
case map[string]any:
// the vv is a map, and the node is a slice
mergedValue := append(vNcNode, vv)
parentNode[key] = mergedValue
case *any:
merged, err := mergeConditional(vv, vNcNode, merge)
if err != nil {
return false, err
}
parentNode[key] = merged
nc.Node = merged
default:
// the vv is an basic JSON type (int, float, string, bool)
vv = append(vNcNode, vv)
parentNode[key] = vv
nc.Node = vv
}
default:
return false, fmt.Errorf("don't know about vnc type %T", vNcNode)
}
} else {
// For map passed as string type, the root is the new key.
if mapFromString {
if err := util.DeleteFromMap(nc.Parent.Node, nc.Parent.KeyToChild); err != nil {
return false, err
}
vm := vv.(map[string]any)
newKey := getTreeRoot(vm)
return false, util.InsertIntoMap(nc.Parent.Node, newKey, vm[newKey])
}
parentNode[key] = vv
nc.Node = vv
}
// TODO `map[interface{}]interface{}` is used by tests in operator/cmd/mesh, we should add our own tests
case map[any]any:
key := nc.Parent.KeyToChild.(string)
parentNode[key] = vv
nc.Node = vv
default:
return false, fmt.Errorf("don't know about type %T", parentNode)
}
return true, nil
}
// mergeConditional returns a merge of newVal and originalVal if merge is true, otherwise it returns newVal.
func mergeConditional(newVal, originalVal any, merge bool) (any, error) {
if !merge || util.IsValueNilOrDefault(originalVal) {
return newVal, nil
}
newS, err := yaml.Marshal(newVal)
if err != nil {
return nil, err
}
if util.IsYAMLEmpty(string(newS)) {
return originalVal, nil
}
originalS, err := yaml.Marshal(originalVal)
if err != nil {
return nil, err
}
if util.IsYAMLEmpty(string(originalS)) {
return newVal, nil
}
mergedS, err := util.OverlayYAML(string(originalS), string(newS))
if err != nil {
return nil, err
}
if util.IsMap(originalVal) {
// For JSON compatibility
out := make(map[string]any)
if err := yaml.Unmarshal([]byte(mergedS), &out); err != nil {
return nil, err
}
return out, nil
}
// For scalars and slices, copy the type
out := originalVal
if err := yaml.Unmarshal([]byte(mergedS), &out); err != nil {
return nil, err
}
return out, nil
}
// find returns the value at path from the given tree, or false if the path does not exist.
func find(treeNode any, path util.Path) (any, bool) {
if len(path) == 0 || treeNode == nil {
return nil, false
}
switch nt := treeNode.(type) {
case map[any]any:
val := nt[path[0]]
if val == nil {
return nil, false
}
if len(path) == 1 {
return val, true
}
return find(val, path[1:])
case map[string]any:
val := nt[path[0]]
if val == nil {
return nil, false
}
if len(path) == 1 {
return val, true
}
return find(val, path[1:])
case []any:
idx, err := strconv.Atoi(path[0])
if err != nil {
return nil, false
}
if idx >= len(nt) {
return nil, false
}
val := nt[idx]
return find(val, path[1:])
default:
return nil, false
}
}
// stringsEqual reports whether the string representations of a and b are equal. a and b may have different types.
func stringsEqual(a, b any) bool {
return fmt.Sprint(a) == fmt.Sprint(b)
}
// matchesRegex reports whether str regex matches pattern.
func matchesRegex(pattern, str any) bool {
match, err := regexp.MatchString(fmt.Sprint(pattern), fmt.Sprint(str))
if err != nil {
return false
}
return match
}
// isSliceOrPtrInterface reports whether v is a slice, a ptr to slice or interface to slice.
func isSliceOrPtrInterface(v any) bool {
vv := reflect.ValueOf(v)
if vv.Kind() == reflect.Ptr {
vv = vv.Elem()
}
if vv.Kind() == reflect.Interface {
vv = vv.Elem()
}
return vv.Kind() == reflect.Slice
}
// isMapOrInterface reports whether v is a map, or interface to a map.
func isMapOrInterface(v any) bool {
vv := reflect.ValueOf(v)
if vv.Kind() == reflect.Interface {
vv = vv.Elem()
}
return vv.Kind() == reflect.Map
}
// tryToUnmarshalStringToYAML tries to unmarshal something that may be a YAML list or map into a structure. If not
// possible, returns original scalar value.
func tryToUnmarshalStringToYAML(s any) (any, bool) {
// If value type is a string it could either be a literal string or a map type passed as a string. Try to unmarshal
// to discover it's the latter.
vv := s
if reflect.TypeOf(vv).Kind() == reflect.String {
sv := strings.Split(vv.(string), "\n")
// Need to be careful not to transform string literals into maps unless they really are maps, since scalar handling
// is different for inserts.
if len(sv) == 1 && strings.Contains(s.(string), ": ") ||
len(sv) > 1 && strings.Contains(s.(string), ":") {
nv := make(map[string]any)
if err := json.Unmarshal([]byte(vv.(string)), &nv); err == nil {
// treat JSON as string
return vv, false
}
if err := yaml2.Unmarshal([]byte(vv.(string)), &nv); err == nil {
return nv, true
}
}
}
// looks like a literal or failed unmarshal, return original type.
return vv, false
}
// getTreeRoot returns the first key found in m. It assumes a single root tree.
func getTreeRoot(m map[string]any) string {
for k := range m {
return k
}
return ""
}

View File

@@ -0,0 +1,843 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tpath
import (
"testing"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"sigs.k8s.io/yaml"
)
func TestWritePathContext(t *testing.T) {
rootYAML := `
a:
b:
- name: n1
value: v1
- name: n2
list:
- v1
- v2
- v3_regex
`
tests := []struct {
desc string
path string
value any
want string
wantFound bool
wantErr string
}{
{
desc: "AddListEntry",
path: `a.b.[name:n2].list`,
value: `foo`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- name: n2
list:
- v1
- v2
- v3_regex
- foo
`,
},
{
desc: "ModifyListEntryValue",
path: `a.b.[name:n1].value`,
value: `v2`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v2
- list:
- v1
- v2
- v3_regex
name: n2
`,
},
{
desc: "ModifyListEntryValueQuoted",
path: `a.b.[name:n1].value`,
value: `v2`,
wantFound: true,
want: `
a:
b:
- name: "n1"
value: v2
- list:
- v1
- v2
- v3_regex
name: n2
`,
},
{
desc: "ModifyListEntry",
path: `a.b.[name:n2].list.[:v2]`,
value: `v3`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- list:
- v1
- v3
- v3_regex
name: n2
`,
},
{
desc: "ModifyListEntryMapValue",
path: `a.b.[name:n2]`,
value: `name: n2
list:
- nk1: nv1
- nk2: nv2`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- name: n2
list:
- nk1: nv1
- nk2: nv2
`,
},
{
desc: "ModifyNthListEntry",
path: `a.b.[1].list.[:v2]`,
value: `v-the-second`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- list:
- v1
- v-the-second
- v3_regex
name: n2
`,
},
{
desc: "ModifyNthLeafListEntry",
path: `a.b.[1].list.[2]`,
value: `v-the-third`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- list:
- v1
- v2
- v-the-third
name: n2
`,
},
{
desc: "ModifyListEntryValueDotless",
path: `a.b[name:n1].value`,
value: `v2`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v2
- list:
- v1
- v2
- v3_regex
name: n2
`,
},
{
desc: "DeleteListEntry",
path: `a.b.[name:n1]`,
wantFound: true,
want: `
a:
b:
- list:
- v1
- v2
- v3_regex
name: n2
`,
},
{
desc: "DeleteListEntryValue",
path: `a.b.[name:n2].list.[:v2]`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- list:
- v1
- v3_regex
name: n2
`,
},
{
desc: "DeleteListEntryIndex",
path: `a.b.[name:n2].list.[1]`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- list:
- v1
- v3_regex
name: n2
`,
},
{
desc: "DeleteListEntryValueRegex",
path: `a.b.[name:n2].list.[:v3]`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- list:
- v1
- v2
name: n2
`,
},
{
desc: "DeleteListLeafEntryBogusIndex",
path: `a.b.[name:n2].list.[-200]`,
wantFound: false,
wantErr: `path a.b.[name:n2].list.[-200]: element [-200] not found`,
},
{
desc: "DeleteListEntryBogusIndex",
path: `a.b.[1000000].list.[:v2]`,
wantFound: false,
wantErr: `index 1000000 exceeds list length 2 at path [1000000].list.[:v2]`,
},
{
desc: "AddMapEntry",
path: `a.new_key`,
value: `new_val`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- name: n2
list:
- v1
- v2
- v3_regex
new_key: new_val
`,
},
{
desc: "AddMapEntryMapValue",
path: `a.new_key`,
value: `new_key:
nk1:
nk2: nv2`,
wantFound: true,
want: `
a:
b:
- name: n1
value: v1
- name: n2
list:
- v1
- v2
- v3_regex
new_key:
nk1:
nk2: nv2
`,
},
{
desc: "ModifyMapEntryMapValue",
path: `a.b`,
value: `nk1:
nk2: nv2`,
wantFound: true,
want: `
a:
nk1:
nk2: nv2
`,
},
{
desc: "DeleteMapEntry",
path: `a.b`,
wantFound: true,
want: `
a: {}
`,
},
{
desc: "path not found",
path: `a.c.[name:n2].list.[:v3]`,
wantFound: false,
wantErr: `path not found at element c in path a.c.[name:n2].list.[:v3]`,
},
{
desc: "error key",
path: `a.b.[].list`,
wantFound: false,
wantErr: `path a.b.[].list: [] is not a valid key:value path element`,
},
{
desc: "invalid index",
path: `a.c.[n2].list.[:v3]`,
wantFound: false,
wantErr: `path not found at element c in path a.c.[n2].list.[:v3]`,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
root := make(map[string]any)
if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
t.Fatal(err)
}
pc, gotFound, gotErr := GetPathContext(root, util.PathFromString(tt.path), false)
if gotErr, wantErr := errToString(gotErr), tt.wantErr; gotErr != wantErr {
t.Fatalf("GetPathContext(%s): gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
}
if gotFound != tt.wantFound {
t.Fatalf("GetPathContext(%s): gotFound:%v, wantFound:%v", tt.desc, gotFound, tt.wantFound)
}
if tt.wantErr != "" || !tt.wantFound {
if tt.want != "" {
t.Error("tt.want is set but never checked")
}
return
}
err := WritePathContext(pc, tt.value, false)
if err != nil {
t.Fatal(err)
}
gotYAML := util.ToYAML(root)
diff := util.YAMLDiff(gotYAML, tt.want)
if diff != "" {
t.Errorf("%s: (got:-, want:+):\n%s\n", tt.desc, diff)
}
})
}
}
func TestWriteNode(t *testing.T) {
testTreeYAML := `
a:
b:
c: val1
list1:
- i1: val1
- i2: val2
- i3a: key1
i3b:
list2:
- i1: val1
- i2: val2
- i3a: key1
i3b:
i1: va11
`
tests := []struct {
desc string
baseYAML string
path string
value string
want string
wantErr string
}{
{
desc: "insert empty",
path: "a.b.c",
value: "val1",
want: `
a:
b:
c: val1
`,
},
{
desc: "overwrite",
baseYAML: testTreeYAML,
path: "a.b.c",
value: "val2",
want: `
a:
b:
c: val2
list1:
- i1: val1
- i2: val2
- i3a: key1
i3b:
list2:
- i1: val1
- i2: val2
- i3a: key1
i3b:
i1: va11
`,
},
{
desc: "partial create",
baseYAML: testTreeYAML,
path: "a.b.d",
value: "val3",
want: `
a:
b:
c: val1
d: val3
list1:
- i1: val1
- i2: val2
- i3a: key1
i3b:
list2:
- i1: val1
- i2: val2
- i3a: key1
i3b:
i1: va11
`,
},
{
desc: "list keys",
baseYAML: testTreeYAML,
path: "a.b.list1.[i3a:key1].i3b.list2.[i3a:key1].i3b.i1",
value: "val2",
want: `
a:
b:
c: val1
list1:
- i1: val1
- i2: val2
- i3a: key1
i3b:
list2:
- i1: val1
- i2: val2
- i3a: key1
i3b:
i1: val2
`,
},
// For https://github.com/istio/istio/issues/20950
{
desc: "with initial list",
baseYAML: `
components:
ingressGateways:
- enabled: true
`,
path: "components.ingressGateways[0].enabled",
value: "false",
want: `
components:
ingressGateways:
- enabled: "false"
`,
},
{
desc: "no initial list",
baseYAML: "",
path: "components.ingressGateways[0].enabled",
value: "false",
want: `
components:
ingressGateways:
- enabled: "false"
`,
},
{
desc: "no initial list for entry",
baseYAML: `
a: {}
`,
path: "a.list.[0]",
value: "v1",
want: `
a:
list:
- v1
`,
},
{
desc: "ExtendNthLeafListEntry",
baseYAML: `
a:
list:
- v1
`,
path: `a.list.[1]`,
value: `v2`,
want: `
a:
list:
- v1
- v2
`,
},
{
desc: "ExtendLeafListEntryLargeIndex",
baseYAML: `
a:
list:
- v1
`,
path: `a.list.[999]`,
value: `v2`,
want: `
a:
list:
- v1
- v2
`,
},
{
desc: "ExtendLeafListEntryNegativeIndex",
baseYAML: `
a:
list:
- v1
`,
path: `a.list.[-1]`,
value: `v2`,
want: `
a:
list:
- v1
- v2
`,
},
{
desc: "ExtendNthListEntry",
baseYAML: `
a:
list:
- name: foo
`,
path: `a.list.[1].name`,
value: `bar`,
want: `
a:
list:
- name: foo
- name: bar
`,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
root := make(map[string]any)
if tt.baseYAML != "" {
if err := yaml.Unmarshal([]byte(tt.baseYAML), &root); err != nil {
t.Fatal(err)
}
}
p := util.PathFromString(tt.path)
err := WriteNode(root, p, tt.value)
if gotErr, wantErr := errToString(err), tt.wantErr; gotErr != wantErr {
t.Errorf("%s: gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
return
}
if got, want := util.ToYAML(root), tt.want; err == nil && util.YAMLDiff(got, want) != "" {
t.Errorf("%s: got:\n%s\nwant:\n%s\ndiff:\n%s\n", tt.desc, got, want, util.YAMLDiff(got, want))
}
})
}
}
func TestMergeNode(t *testing.T) {
testTreeYAML := `
a:
b:
c: val1
list1:
- i1: val1
- i2: val2
`
tests := []struct {
desc string
baseYAML string
path string
value string
want string
wantErr string
}{
{
desc: "merge list entry",
baseYAML: testTreeYAML,
path: "a.b.list1.[i1:val1]",
value: `
i2b: val2`,
want: `
a:
b:
c: val1
list1:
- i1: val1
i2b: val2
- i2: val2
`,
},
{
desc: "merge list 2",
baseYAML: testTreeYAML,
path: "a.b.list1",
value: `
i3:
a: val3
`,
want: `
a:
b:
c: val1
list1:
- i1: val1
- i2: val2
- i3:
a: val3
`,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
root := make(map[string]any)
if tt.baseYAML != "" {
if err := yaml.Unmarshal([]byte(tt.baseYAML), &root); err != nil {
t.Fatal(err)
}
}
p := util.PathFromString(tt.path)
iv := make(map[string]any)
err := yaml.Unmarshal([]byte(tt.value), &iv)
if err != nil {
t.Fatal(err)
}
err = MergeNode(root, p, iv)
if gotErr, wantErr := errToString(err), tt.wantErr; gotErr != wantErr {
t.Errorf("%s: gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
return
}
if got, want := util.ToYAML(root), tt.want; err == nil && util.YAMLDiff(got, want) != "" {
t.Errorf("%s: got:\n%s\nwant:\n%s\ndiff:\n%s\n", tt.desc, got, want, util.YAMLDiff(got, want))
}
})
}
}
// errToString returns the string representation of err and the empty string if
// err is nil.
func errToString(err error) string {
if err == nil {
return ""
}
return err.Error()
}
// TestSecretVolumes simulates https://github.com/istio/istio/issues/20381
func TestSecretVolumes(t *testing.T) {
rootYAML := `
values:
gateways:
istio-egressgateway:
secretVolumes: []
`
root := make(map[string]any)
if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
t.Fatal(err)
}
overrides := []struct {
path string
value any
}{
{
path: "values.gateways.istio-egressgateway.secretVolumes[0].name",
value: "egressgateway-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[0].secretName",
value: "istio-egressgateway-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[0].mountPath",
value: "/etc/istio/egressgateway-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[1].name",
value: "egressgateway-ca-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[1].secretName",
value: "istio-egressgateway-ca-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[1].mountPath",
value: "/etc/istio/egressgateway-ca-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[2].name",
value: "nginx-client-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[2].secretName",
value: "nginx-client-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[2].mountPath",
value: "/etc/istio/nginx-client-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[3].name",
value: "nginx-ca-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[3].secretName",
value: "nginx-ca-certs",
},
{
path: "values.gateways.istio-egressgateway.secretVolumes[3].mountPath",
value: "/etc/istio/nginx-ca-certs",
},
}
for _, override := range overrides {
pc, _, err := GetPathContext(root, util.PathFromString(override.path), true)
if err != nil {
t.Fatalf("GetPathContext(%q): %v", override.path, err)
}
err = WritePathContext(pc, override.value, false)
if err != nil {
t.Fatalf("WritePathContext(%q): %v", override.path, err)
}
}
want := `
values:
gateways:
istio-egressgateway:
secretVolumes:
- mountPath: /etc/istio/egressgateway-certs
name: egressgateway-certs
secretName: istio-egressgateway-certs
- mountPath: /etc/istio/egressgateway-ca-certs
name: egressgateway-ca-certs
secretName: istio-egressgateway-ca-certs
- mountPath: /etc/istio/nginx-client-certs
name: nginx-client-certs
secretName: nginx-client-certs
- mountPath: /etc/istio/nginx-ca-certs
name: nginx-ca-certs
secretName: nginx-ca-certs
`
gotYAML := util.ToYAML(root)
diff := util.YAMLDiff(gotYAML, want)
if diff != "" {
t.Errorf("TestSecretVolumes: diff:\n%s\n", diff)
}
}
// Simulates https://github.com/istio/istio/issues/19196
func TestWriteEscapedPathContext(t *testing.T) {
rootYAML := `
values:
sidecarInjectorWebhook:
injectedAnnotations: {}
`
tests := []struct {
desc string
path string
value any
want string
wantFound bool
wantErr string
}{
{
desc: "ModifyEscapedPathValue",
path: `values.sidecarInjectorWebhook.injectedAnnotations.container\.apparmor\.security\.beta\.kubernetes\.io/istio-proxy`,
value: `runtime/default`,
wantFound: true,
want: `
values:
sidecarInjectorWebhook:
injectedAnnotations:
container.apparmor.security.beta.kubernetes.io/istio-proxy: runtime/default
`,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
root := make(map[string]any)
if err := yaml.Unmarshal([]byte(rootYAML), &root); err != nil {
t.Fatal(err)
}
pc, gotFound, gotErr := GetPathContext(root, util.PathFromString(tt.path), false)
if gotErr, wantErr := errToString(gotErr), tt.wantErr; gotErr != wantErr {
t.Fatalf("GetPathContext(%s): gotErr:%s, wantErr:%s", tt.desc, gotErr, wantErr)
}
if gotFound != tt.wantFound {
t.Fatalf("GetPathContext(%s): gotFound:%v, wantFound:%v", tt.desc, gotFound, tt.wantFound)
}
if tt.wantErr != "" || !tt.wantFound {
return
}
err := WritePathContext(pc, tt.value, false)
if err != nil {
t.Fatal(err)
}
gotYAML := util.ToYAML(root)
diff := util.YAMLDiff(gotYAML, tt.want)
if diff != "" {
t.Errorf("%s: diff:\n%s\n", tt.desc, diff)
}
})
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tpath
import (
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"gopkg.in/yaml.v2"
yaml2 "sigs.k8s.io/yaml"
)
// AddSpecRoot adds a root node called "spec" to the given tree and returns the resulting tree.
func AddSpecRoot(tree string) (string, error) {
t, nt := make(map[string]any), make(map[string]any)
if err := yaml.Unmarshal([]byte(tree), &t); err != nil {
return "", err
}
nt["spec"] = t
out, err := yaml.Marshal(nt)
if err != nil {
return "", err
}
return string(out), nil
}
// GetSpecSubtree returns the subtree under "spec".
func GetSpecSubtree(yml string) (string, error) {
return GetConfigSubtree(yml, "spec")
}
// GetConfigSubtree returns the subtree at the given path.
func GetConfigSubtree(manifest, path string) (string, error) {
root := make(map[string]any)
if err := yaml2.Unmarshal([]byte(manifest), &root); err != nil {
return "", err
}
nc, _, err := GetPathContext(root, util.PathFromString(path), false)
if err != nil {
return "", err
}
out, err := yaml2.Marshal(nc.Node)
if err != nil {
return "", err
}
return string(out), nil
}

View File

@@ -0,0 +1,122 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tpath
import (
"errors"
"testing"
)
func TestAddSpecRoot(t *testing.T) {
tests := []struct {
desc string
in string
expect string
err error
}{
{
desc: "empty",
in: ``,
expect: `spec: {}
`,
err: nil,
},
{
desc: "add-root",
in: `
a: va
b: foo`,
expect: `spec:
a: va
b: foo
`,
err: nil,
},
{
desc: "err",
in: `i can't be yaml, can I?`,
expect: ``,
err: errors.New(""),
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got, err := AddSpecRoot(tt.in); got != tt.expect ||
((err != nil && tt.err == nil) || (err == nil && tt.err != nil)) {
t.Errorf("%s AddSpecRoot(%s) => %s, want %s", tt.desc, tt.in, got, tt.expect)
}
})
}
}
func TestGetConfigSubtree(t *testing.T) {
tests := []struct {
desc string
manifest string
path string
expect string
err bool
}{
{
desc: "empty",
manifest: ``,
path: ``,
expect: `{}
`,
err: false,
},
{
desc: "subtree",
manifest: `
a:
b:
- name: n1
value: v2
- list:
- v1
- v2
- v3_regex
name: n2
`,
path: `a`,
expect: `b:
- name: n1
value: v2
- list:
- v1
- v2
- v3_regex
name: n2
`,
err: false,
},
{
desc: "err",
manifest: "not-yaml",
path: "not-subnode",
expect: ``,
err: true,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got, err := GetConfigSubtree(tt.manifest, tt.path); got != tt.expect || (err == nil) == tt.err {
t.Errorf("%s GetConfigSubtree(%s, %s) => %s, want %s", tt.desc, tt.manifest, tt.path, got, tt.expect)
}
})
}
}

202
pkg/cmd/hgctl/install.go Normal file
View File

@@ -0,0 +1,202 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
import (
"fmt"
"io"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra"
)
const (
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).`
// manifestsFlagHelpStr is the command line description for --manifests
manifestsFlagHelpStr = `Specify a path to a directory of profiles
(e.g. ~/Downloads/higress/manifests).`
outputHelpstr = "Specify a file to write profile yaml"
profileNameK8s = "k8s"
profileNameLocalK8s = "local-k8s"
)
type InstallArgs struct {
InFilenames []string
// KubeConfigPath is the path to kube config file.
KubeConfigPath string
// Context is the cluster context in the kube config
Context string
// Set is a string with element format "path=value" where path is an profile path and the value is a
// value to set the node at that path to.
Set []string
// ManifestsPath is a path to a ManifestsPath and profiles directory in the local filesystem with a release tgz.
ManifestsPath string
}
func (a *InstallArgs) String() string {
var b strings.Builder
b.WriteString("KubeConfigPath: " + a.KubeConfigPath + "\n")
b.WriteString("Context: " + a.Context + "\n")
b.WriteString("Set: " + fmt.Sprint(a.Set) + "\n")
b.WriteString("ManifestsPath: " + a.ManifestsPath + "\n")
return b.String()
}
func addInstallFlags(cmd *cobra.Command, args *InstallArgs) {
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
}
// --manifests is an alias for --set installPackagePath=
func applyFlagAliases(flags []string, manifestsPath string) []string {
if manifestsPath != "" {
flags = append(flags, fmt.Sprintf("installPackagePath=%s", manifestsPath))
}
return flags
}
// newInstallCmd generates a higress install manifest and applies it to a cluster
func newInstallCmd() *cobra.Command {
iArgs := &InstallArgs{}
installCmd := &cobra.Command{
Use: "install",
Short: "Applies an higress manifest, installing or reconfiguring higress on a cluster.",
Long: "The install command generates an higress install manifest and applies it to a cluster.",
// nolint: lll
Example: ` # Apply a default higress installation
hgctl install
# Install higress on local kubernetes cluster
hgctl install --set profile=local-k8s
# To override profile setting
hgctl install --set profile=local-k8s --set global.enableIstioAPI=true --set gateway.replicas=2"
# To override helm setting
hgctl install --set profile=local-k8s --set values.global.proxy.resources.requsts.cpu=500m"
`,
Args: cobra.ExactArgs(0),
PreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return Install(cmd.OutOrStdout(), iArgs)
},
}
addInstallFlags(installCmd, iArgs)
flags := installCmd.Flags()
options.AddKubeConfigFlags(flags)
return installCmd
}
func Install(writer io.Writer, iArgs *InstallArgs) error {
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
// check profileName
psf := helm.GetValueForSetFlag(setFlags, "profile")
if len(psf) == 0 {
psf = promptProfileName(writer)
setFlags = append(setFlags, fmt.Sprintf("profile=%s", psf))
}
if !promptInstall(writer, psf) {
return nil
}
_, profile, profileName, err := helm.GenerateConfig(iArgs.InFilenames, setFlags)
if err != nil {
return fmt.Errorf("generate config: %v", err)
}
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
err = profile.Validate()
if err != nil {
return err
}
err = InstallManifests(profile, writer)
if err != nil {
return fmt.Errorf("failed to install manifests: %v", err)
}
return nil
}
func promptInstall(writer io.Writer, profileName string) bool {
answer := ""
for {
fmt.Fprintf(writer, "\nThis will install Higress \"%s\" profile into the cluster. \nProceed? (y/N)", profileName)
fmt.Scanln(&answer)
if strings.TrimSpace(answer) == "y" {
fmt.Fprintf(writer, "\n")
return true
}
if strings.TrimSpace(answer) == "N" {
fmt.Fprintf(writer, "Cancelled.\n")
return false
}
}
}
func promptProfileName(writer io.Writer) string {
answer := ""
fmt.Fprintf(writer, "Please select higress install configration profile:\n")
fmt.Fprintf(writer, "1.Install higress to local kubernetes cluster like kind etc.\n")
fmt.Fprintf(writer, "2.Install higress to kubernetes cluster\n")
for {
fmt.Fprintf(writer, "Please input 1 or 2 to select, input your selection:")
fmt.Scanln(&answer)
if strings.TrimSpace(answer) == "1" {
return profileNameLocalK8s
}
if strings.TrimSpace(answer) == "2" {
return profileNameK8s
}
}
}
func InstallManifests(profile *helm.Profile, writer io.Writer) error {
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("failed to build kubernetes client: %w", err)
}
op, err := installer.NewInstaller(profile, cliClient, writer, false)
if err != nil {
return err
}
if err := op.Run(); err != nil {
return err
}
manifestMap, err := op.RenderManifests()
if err != nil {
return err
}
fmt.Fprintf(writer, "\n⌛ Processing installation... \n\n")
if err := op.ApplyManifests(manifestMap); err != nil {
return err
}
fmt.Fprintf(writer, "\n🎊 Install All Resources Complete!\n")
return nil
}

View File

@@ -0,0 +1,112 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package installer
import (
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"sigs.k8s.io/yaml"
)
type ComponentName string
var ComponentMap = map[ComponentName]struct{}{
Higress: {},
Istio: {},
}
type Component interface {
// ComponentName returns the name of the component.
ComponentName() ComponentName
// Namespace returns the namespace for the component.
Namespace() string
// Enabled reports whether the component is enabled.
Enabled() bool
// Run starts the component. Must be called before the component is used.
Run() error
RenderManifest() (string, error)
}
type ComponentOptions struct {
Name string
Namespace string
// local
ChartPath string
// remote
RepoURL string
ChartName string
Version string
Quiet bool
}
type ComponentOption func(*ComponentOptions)
func WithComponentNamespace(namespace string) ComponentOption {
return func(opts *ComponentOptions) {
opts.Namespace = namespace
}
}
func WithComponentChartPath(path string) ComponentOption {
return func(opts *ComponentOptions) {
opts.ChartPath = path
}
}
func WithComponentChartName(chartName string) ComponentOption {
return func(opts *ComponentOptions) {
opts.ChartName = chartName
}
}
func WithComponentRepoURL(url string) ComponentOption {
return func(opts *ComponentOptions) {
opts.RepoURL = url
}
}
func WithComponentVersion(version string) ComponentOption {
return func(opts *ComponentOptions) {
opts.Version = version
}
}
func WithQuiet() ComponentOption {
return func(opts *ComponentOptions) {
opts.Quiet = true
}
}
func renderComponentManifest(spec any, renderer helm.Renderer, addOn bool, name ComponentName, namespace string) (string, error) {
var valsBytes []byte
var valsYaml string
var err error
if yamlString, ok := spec.(string); ok {
valsYaml = yamlString
} else {
if !util.IsValueNil(spec) {
valsBytes, err = yaml.Marshal(spec)
if err != nil {
return "", err
}
valsYaml = string(valsBytes)
}
}
final, err := renderer.RenderManifest(valsYaml)
if err != nil {
return "", err
}
return final, nil
}

View File

@@ -0,0 +1,131 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package installer
import (
"fmt"
"io"
"os"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
)
const (
Higress ComponentName = "higress"
)
type HigressComponent struct {
profile *helm.Profile
started bool
opts *ComponentOptions
renderer helm.Renderer
writer io.Writer
}
func (h *HigressComponent) ComponentName() ComponentName {
return Higress
}
func (h *HigressComponent) Namespace() string {
return h.opts.Namespace
}
func (h *HigressComponent) Enabled() bool {
return true
}
func (h *HigressComponent) Run() error {
// Parse latest version
if h.opts.Version == helm.RepoLatestVersion {
latestVersion, err := helm.ParseLatestVersion(h.opts.RepoURL, h.opts.Version)
if err != nil {
return err
}
if !h.opts.Quiet {
fmt.Fprintf(h.writer, "⚡️ Fetching Higress Helm Chart latest version \"%s\" \n", latestVersion)
}
// Reset Helm Chart version
h.opts.Version = latestVersion
h.renderer.SetVersion(latestVersion)
}
if !h.opts.Quiet {
fmt.Fprintf(h.writer, "🏄 Downloading Higress Helm Chart version: %s, url: %s\n", h.opts.Version, h.opts.RepoURL)
}
if err := h.renderer.Init(); err != nil {
return err
}
h.started = true
return nil
}
func (h *HigressComponent) RenderManifest() (string, error) {
if !h.started {
return "", nil
}
if !h.opts.Quiet {
fmt.Fprintf(h.writer, "📦 Rendering Higress Helm Chart\n")
}
valsYaml, err := h.profile.ValuesYaml()
if err != nil {
return "", err
}
manifest, err2 := renderComponentManifest(valsYaml, h.renderer, true, h.ComponentName(), h.opts.Namespace)
if err2 != nil {
return "", err
}
return manifest, nil
}
func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
var renderer helm.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = helm.NewRemoteRenderer(
helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace),
helm.WithRepoURL(newOpts.RepoURL),
helm.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = helm.NewLocalRenderer(
helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace),
helm.WithVersion(newOpts.Version),
helm.WithFS(os.DirFS(newOpts.ChartPath)),
helm.WithDir(string(Higress)),
)
if err != nil {
return nil, err
}
}
higressComponent := &HigressComponent{
profile: profile,
renderer: renderer,
opts: newOpts,
writer: writer,
}
return higressComponent, nil
}

View File

@@ -0,0 +1,213 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package installer
import (
"errors"
"fmt"
"io"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/object"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
)
type Installer struct {
started bool
components map[ComponentName]Component
kubeCli kubernetes.CLIClient
profile *helm.Profile
writer io.Writer
}
// Run must be invoked before invoking other functions.
func (o *Installer) Run() error {
for name, component := range o.components {
if !component.Enabled() {
continue
}
if err := component.Run(); err != nil {
return fmt.Errorf("component %s run failed, err: %s", name, err)
}
}
o.started = true
return nil
}
// RenderManifests renders component manifests specified by profile.
func (o *Installer) RenderManifests() (map[ComponentName]string, error) {
if !o.started {
return nil, errors.New("HigressOperator is not running")
}
res := make(map[ComponentName]string)
for name, component := range o.components {
if !component.Enabled() {
continue
}
manifest, err := component.RenderManifest()
if err != nil {
return nil, fmt.Errorf("component %s RenderManifest err: %v", name, err)
}
res[name] = manifest
}
return res, nil
}
// GenerateManifests generates component manifests to k8s cluster
func (o *Installer) GenerateManifests(manifestMap map[ComponentName]string) error {
if o.kubeCli == nil {
return errors.New("no injected k8s cli into Installer")
}
for _, manifest := range manifestMap {
fmt.Fprint(o.writer, manifest)
}
return nil
}
// ApplyManifests apply component manifests to k8s cluster
func (o *Installer) ApplyManifests(manifestMap map[ComponentName]string) error {
if o.kubeCli == nil {
return errors.New("no injected k8s cli into Installer")
}
for name, manifest := range manifestMap {
namespace := o.components[name].Namespace()
if err := o.applyManifest(manifest, namespace); err != nil {
return fmt.Errorf("component %s ApplyManifest err: %v", name, err)
}
}
return nil
}
func (o *Installer) applyManifest(manifest string, ns string) error {
if err := o.kubeCli.CreateNamespace(ns); err != nil {
return err
}
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
if err != nil {
return err
}
for _, obj := range objs {
// check namespaced object if namespace property has been existed
if obj.Namespace == "" && o.isNamespacedObject(obj) {
obj.Namespace = ns
obj.UnstructuredObject().SetNamespace(ns)
}
if o.isNamespacedObject(obj) {
fmt.Fprintf(o.writer, "✔️ Installed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
} else {
fmt.Fprintf(o.writer, "✔️ Installed %s::%s.\n", obj.Kind, obj.Name)
}
if err := o.kubeCli.ApplyObject(obj.UnstructuredObject()); err != nil {
return err
}
}
return nil
}
// DeleteManifests delete component manifests to k8s cluster
func (o *Installer) DeleteManifests(manifestMap map[ComponentName]string) error {
if o.kubeCli == nil {
return errors.New("no injected k8s cli into Installer")
}
for name, manifest := range manifestMap {
namespace := o.components[name].Namespace()
if err := o.deleteManifest(manifest, namespace); err != nil {
return fmt.Errorf("component %s DeleteManifest err: %v", name, err)
}
}
return nil
}
// deleteManifest delete manifest to certain namespace
func (o *Installer) deleteManifest(manifest string, ns string) error {
objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest)
if err != nil {
return err
}
for _, obj := range objs {
// check namespaced object if namespace property has been existed
if obj.Namespace == "" && o.isNamespacedObject(obj) {
obj.Namespace = ns
obj.UnstructuredObject().SetNamespace(ns)
}
if o.isNamespacedObject(obj) {
fmt.Fprintf(o.writer, "✔️ Removed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace)
} else {
fmt.Fprintf(o.writer, "✔️ Removed %s::%s.\n", obj.Kind, obj.Name)
}
if err := o.kubeCli.DeleteObject(obj.UnstructuredObject()); err != nil {
return err
}
}
return nil
}
func (o *Installer) isNamespacedObject(obj *object.K8sObject) bool {
if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" {
return true
}
return false
}
func NewInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer, quiet bool) (*Installer, error) {
if profile == nil {
return nil, errors.New("install profile is empty")
}
// initialize components
components := make(map[ComponentName]Component)
opts := []ComponentOption{
WithComponentNamespace(profile.Global.Namespace),
WithComponentChartPath(profile.InstallPackagePath),
WithComponentVersion(profile.Charts.Higress.Version),
WithComponentRepoURL(profile.Charts.Higress.Url),
WithComponentChartName(profile.Charts.Higress.Name),
}
if quiet {
opts = append(opts, WithQuiet())
}
higressComponent, err := NewHigressComponent(profile, writer, opts...)
if err != nil {
return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err)
}
components[Higress] = higressComponent
if profile.IstioEnabled() {
opts := []ComponentOption{
WithComponentNamespace(profile.Global.IstioNamespace),
WithComponentChartPath(profile.InstallPackagePath),
WithComponentVersion(profile.Charts.Istio.Version),
WithComponentRepoURL(profile.Charts.Istio.Url),
WithComponentChartName(profile.Charts.Istio.Name),
}
if quiet {
opts = append(opts, WithQuiet())
}
istioCRDComponent, err := NewIstioCRDComponent(profile, writer, opts...)
if err != nil {
return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err)
}
components[Istio] = istioCRDComponent
}
op := &Installer{
profile: profile,
components: components,
kubeCli: cli,
writer: writer,
}
return op, nil
}

View File

@@ -0,0 +1,113 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package installer
import (
"fmt"
"io"
"os"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
)
const (
Istio ComponentName = "istio"
)
type IstioCRDComponent struct {
profile *helm.Profile
started bool
opts *ComponentOptions
renderer helm.Renderer
writer io.Writer
}
func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) {
newOpts := &ComponentOptions{}
for _, opt := range opts {
opt(newOpts)
}
var renderer helm.Renderer
var err error
if newOpts.RepoURL != "" {
renderer, err = helm.NewRemoteRenderer(
helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace),
helm.WithRepoURL(newOpts.RepoURL),
helm.WithVersion(newOpts.Version),
)
if err != nil {
return nil, err
}
} else {
renderer, err = helm.NewLocalRenderer(
helm.WithName(newOpts.ChartName),
helm.WithNamespace(newOpts.Namespace),
helm.WithVersion(newOpts.Version),
helm.WithFS(os.DirFS(newOpts.ChartPath)),
helm.WithDir(string(Istio)),
)
if err != nil {
return nil, err
}
}
istioComponent := &IstioCRDComponent{
profile: profile,
renderer: renderer,
opts: newOpts,
writer: writer,
}
return istioComponent, nil
}
func (i *IstioCRDComponent) ComponentName() ComponentName {
return Istio
}
func (i *IstioCRDComponent) Namespace() string {
return i.opts.Namespace
}
func (i *IstioCRDComponent) Enabled() bool {
return true
}
func (i *IstioCRDComponent) Run() error {
if !i.opts.Quiet {
fmt.Fprintf(i.writer, "🏄 Downloading Istio Helm Chart version: %s, url: %s\n", i.opts.Version, i.opts.RepoURL)
}
if err := i.renderer.Init(); err != nil {
return err
}
i.started = true
return nil
}
func (i *IstioCRDComponent) RenderManifest() (string, error) {
if !i.started {
return "", nil
}
if !i.opts.Quiet {
fmt.Fprintf(i.writer, "📦 Rendering Istio Helm Chart\n")
}
values := make(map[string]any)
manifest, err := renderComponentManifest(values, i.renderer, false, i.ComponentName(), i.opts.Namespace)
if err != nil {
return "", err
}
return manifest, nil
}

View File

@@ -19,17 +19,21 @@ import (
"context"
"fmt"
"strings"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
kubescheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/client-go/util/retry"
ctrClient "sigs.k8s.io/controller-runtime/pkg/client"
)
type CLIClient interface {
@@ -44,6 +48,15 @@ type CLIClient interface {
// PodExec takes a command and the pod data to run the command in the specified pod.
PodExec(namespacedName types.NamespacedName, container string, command string) (stdout string, stderr string, err error)
// ApplyObject creates or updates unstructured object
ApplyObject(obj *unstructured.Unstructured) error
// DeleteObject delete unstructured object
DeleteObject(obj *unstructured.Unstructured) error
// CreateNamespace create namespace
CreateNamespace(namespace string) error
}
var _ CLIClient = &client{}
@@ -52,6 +65,7 @@ type client struct {
config *rest.Config
restClient *rest.RESTClient
kube kubernetes.Interface
ctrClient ctrClient.Client
}
func NewCLIClient(clientConfig clientcmd.ClientConfig) (CLIClient, error) {
@@ -80,33 +94,13 @@ func newClientInternal(clientConfig clientcmd.ClientConfig) (*client, error) {
return nil, err
}
c.ctrClient, err = ctrClient.New(c.config, ctrClient.Options{})
if err != nil {
return nil, err
}
return &c, err
}
func setRestDefaults(config *rest.Config) *rest.Config {
if config.GroupVersion == nil || config.GroupVersion.Empty() {
config.GroupVersion = &corev1.SchemeGroupVersion
}
if len(config.APIPath) == 0 {
if len(config.GroupVersion.Group) == 0 {
config.APIPath = "/api"
} else {
config.APIPath = "/apis"
}
}
if len(config.ContentType) == 0 {
config.ContentType = runtime.ContentTypeJSON
}
if config.NegotiatedSerializer == nil {
// This codec factory ensures the resources are not converted. Therefore, resources
// will not be round-tripped through internal versions. Defaulting does not happen
// on the client.
config.NegotiatedSerializer = serializer.NewCodecFactory(kubescheme.Scheme).WithoutConversion()
}
return config
}
func (c *client) RESTConfig() *rest.Config {
if c.config == nil {
return nil
@@ -170,3 +164,85 @@ func (c *client) PodExec(namespacedName types.NamespacedName, container string,
stderr = stderrBuf.String()
return
}
// DeleteObject delete unstructured object
func (c *client) DeleteObject(obj *unstructured.Unstructured) error {
err := c.ctrClient.Delete(context.TODO(), obj, ctrClient.PropagationPolicy(metav1.DeletePropagationBackground))
if err != nil {
if !errors.IsNotFound(err) {
return err
}
}
return nil
}
// ApplyObject creates or updates unstructured object
func (c *client) ApplyObject(obj *unstructured.Unstructured) error {
if obj.GetKind() == "List" {
objList, err := obj.ToList()
if err != nil {
return err
}
for _, item := range objList.Items {
if err := c.ApplyObject(&item); err != nil {
return err
}
}
return nil
}
key := ctrClient.ObjectKeyFromObject(obj)
receiver := &unstructured.Unstructured{}
receiver.SetGroupVersionKind(obj.GroupVersionKind())
if err := retry.RetryOnConflict(wait.Backoff{
Duration: time.Millisecond * 10,
Factor: 2,
Steps: 3,
}, func() error {
if err := c.ctrClient.Get(context.Background(), key, receiver); err != nil {
if errors.IsNotFound(err) {
if err := c.ctrClient.Create(context.Background(), obj); err != nil {
return err
}
}
return nil
}
if err := applyOverlay(receiver, obj); err != nil {
return err
}
if err := c.ctrClient.Update(context.Background(), receiver); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// CreateNamespace create namespace
func (c *client) CreateNamespace(namespace string) error {
key := ctrClient.ObjectKey{
Namespace: metav1.NamespaceSystem,
Name: namespace,
}
if err := c.ctrClient.Get(context.Background(), key, &corev1.Namespace{}); err != nil {
if errors.IsNotFound(err) {
nsObj := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Namespace: metav1.NamespaceSystem,
Name: namespace,
},
}
if err := c.ctrClient.Create(context.Background(), nsObj); err != nil {
return err
}
return nil
}
return fmt.Errorf("failed to check if namespace %v exists: %v", namespace, err)
}
return nil
}

View File

@@ -0,0 +1,157 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package kubernetes
import (
"fmt"
"strconv"
"strings"
jsonpatch "github.com/evanphx/json-patch/v5"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
kubescheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/scheme"
)
// applyOverlay applies an overlay using JSON patch strategy over the current Object in place.
func applyOverlay(current, overlay *unstructured.Unstructured) error {
cj, err := runtime.Encode(unstructured.UnstructuredJSONScheme, current)
if err != nil {
return err
}
overlayUpdated := overlay.DeepCopy()
if strings.EqualFold(current.GetKind(), "service") {
if err := saveClusterIP(current, overlayUpdated); err != nil {
return err
}
saveNodePorts(current, overlayUpdated)
}
if current.GetKind() == "PersistentVolumeClaim" {
if err := savePersistentVolumeClaim(current, overlayUpdated); err != nil {
return err
}
}
uj, err := runtime.Encode(unstructured.UnstructuredJSONScheme, overlayUpdated)
if err != nil {
return err
}
merged, err := jsonpatch.MergePatch(cj, uj)
if err != nil {
return err
}
return runtime.DecodeInto(unstructured.UnstructuredJSONScheme, merged, current)
}
// createPortMap returns a map, mapping the value of the port and value of the nodePort
func createPortMap(current *unstructured.Unstructured) map[string]uint32 {
portMap := make(map[string]uint32)
svc := &corev1.Service{}
if err := scheme.Scheme.Convert(current, svc, nil); err != nil {
return portMap
}
for _, p := range svc.Spec.Ports {
portMap[strconv.Itoa(int(p.Port))] = uint32(p.NodePort)
}
return portMap
}
// savePersistentVolumeClaim copies the storageClassName from the current cluster into the overlay
func savePersistentVolumeClaim(current, overlay *unstructured.Unstructured) error {
// Save the value of spec.storageClassName set by the cluster
if storageClassName, found, err := unstructured.NestedString(current.Object, "spec",
"storageClassName"); err != nil {
return err
} else if found {
if _, _, err2 := unstructured.NestedString(overlay.Object, "spec",
"storageClassName"); err2 != nil {
// override when overlay storageClassName property is not existed
if err3 := unstructured.SetNestedField(overlay.Object, storageClassName, "spec",
"storageClassName"); err3 != nil {
return err3
}
}
}
return nil
}
// saveNodePorts transfers the port values from the current cluster into the overlay
func saveNodePorts(current, overlay *unstructured.Unstructured) {
portMap := createPortMap(current)
ports, _, _ := unstructured.NestedFieldNoCopy(overlay.Object, "spec", "ports")
portList, ok := ports.([]any)
if !ok {
return
}
for _, port := range portList {
m, ok := port.(map[string]any)
if !ok {
continue
}
if nodePortNum, ok := m["nodePort"]; ok && fmt.Sprintf("%v", nodePortNum) == "0" {
if portNum, ok := m["port"]; ok {
if v, ok := portMap[fmt.Sprintf("%v", portNum)]; ok {
m["nodePort"] = v
}
}
}
}
}
// saveClusterIP copies the cluster IP from the current cluster into the overlay
func saveClusterIP(current, overlay *unstructured.Unstructured) error {
// Save the value of spec.clusterIP set by the cluster
if clusterIP, found, err := unstructured.NestedString(current.Object, "spec",
"clusterIP"); err != nil {
return err
} else if found {
if err := unstructured.SetNestedField(overlay.Object, clusterIP, "spec",
"clusterIP"); err != nil {
return err
}
}
return nil
}
func setRestDefaults(config *rest.Config) *rest.Config {
if config.GroupVersion == nil || config.GroupVersion.Empty() {
config.GroupVersion = &corev1.SchemeGroupVersion
}
if len(config.APIPath) == 0 {
if len(config.GroupVersion.Group) == 0 {
config.APIPath = "/api"
} else {
config.APIPath = "/apis"
}
}
if len(config.ContentType) == 0 {
config.ContentType = runtime.ContentTypeJSON
}
if config.NegotiatedSerializer == nil {
// This codec factory ensures the resources are not converted. Therefore, resources
// will not be round-tripped through internal versions. Defaulting does not happen
// on the client.
config.NegotiatedSerializer = serializer.NewCodecFactory(kubescheme.Scheme).WithoutConversion()
}
return config
}

View File

@@ -48,6 +48,9 @@ type PortForwarder interface {
// Address returns the address of the local forwarded address.
Address() string
// WaitForStop blocks until connection closed (e.g. control-C interrupt)
WaitForStop()
}
var _ PortForwarder = &localForwarder{}
@@ -153,3 +156,7 @@ func (f *localForwarder) Stop() {
func (f *localForwarder) Address() string {
return fmt.Sprintf("%s:%d", DefaultLocalAddress, f.localPort)
}
func (f *localForwarder) WaitForStop() {
<-f.stopCh
}

143
pkg/cmd/hgctl/manifest.go Normal file
View File

@@ -0,0 +1,143 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
import (
"fmt"
"io"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra"
)
type ManifestArgs struct {
InFilenames []string
// KubeConfigPath is the path to kube config file.
KubeConfigPath string
// Context is the cluster context in the kube config
Context string
// Set is a string with element format "path=value" where path is an profile path and the value is a
// value to set the node at that path to.
Set []string
// ManifestsPath is a path to a ManifestsPath and profiles directory in the local filesystem with a release tgz.
ManifestsPath string
}
func (a *ManifestArgs) String() string {
var b strings.Builder
b.WriteString("KubeConfigPath: " + a.KubeConfigPath + "\n")
b.WriteString("Context: " + a.Context + "\n")
b.WriteString("Set: " + fmt.Sprint(a.Set) + "\n")
b.WriteString("ManifestsPath: " + a.ManifestsPath + "\n")
return b.String()
}
// newManifestCmd generates a higress install manifest and applies it to a cluster
func newManifestCmd() *cobra.Command {
iArgs := &ManifestArgs{}
manifestCmd := &cobra.Command{
Use: "manifest",
Short: "Generate higress manifests.",
Long: "The manifest command generates an higress install manifests.",
}
generate := newManifestGenerateCmd(iArgs)
addManifestFlags(generate, iArgs)
manifestCmd.AddCommand(generate)
return manifestCmd
}
func addManifestFlags(cmd *cobra.Command, args *ManifestArgs) {
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
}
// newManifestGenerateCmd generates a higress install manifest and applies it to a cluster
func newManifestGenerateCmd(iArgs *ManifestArgs) *cobra.Command {
installCmd := &cobra.Command{
Use: "generate",
Short: "Generate higress manifests.",
Long: "The manifest generate command generates higress install manifests.",
// nolint: lll
Example: ` # Generate higress manifests
hgctl manifest generate
`,
Args: cobra.ExactArgs(0),
PreRunE: func(cmd *cobra.Command, args []string) error {
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return generate(cmd.OutOrStdout(), iArgs)
},
}
return installCmd
}
func generate(writer io.Writer, iArgs *ManifestArgs) error {
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
// check profileName
psf := helm.GetValueForSetFlag(setFlags, "profile")
if len(psf) == 0 {
setFlags = append(setFlags, fmt.Sprintf("profile=%s", helm.InstallLocalK8s))
}
_, profile, _, err := helm.GenerateConfig(iArgs.InFilenames, setFlags)
if err != nil {
return fmt.Errorf("generate config: %v", err)
}
err = profile.Validate()
if err != nil {
return err
}
err = genManifests(profile, writer)
if err != nil {
return fmt.Errorf("failed to install manifests: %v", err)
}
return nil
}
func genManifests(profile *helm.Profile, writer io.Writer) error {
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("failed to build kubernetes client: %w", err)
}
op, err := installer.NewInstaller(profile, cliClient, writer, true)
if err != nil {
return err
}
if err := op.Run(); err != nil {
return err
}
manifestMap, err := op.RenderManifests()
if err != nil {
return err
}
if err := op.GenerateManifests(manifestMap); err != nil {
return err
}
return nil
}

View File

@@ -12,16 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package flags
package manifests
import (
"flag"
"embed"
"io/fs"
"os"
)
var (
IngressClassName = flag.String("ingress-class", "higress", "Name of IngressClass to use for tests")
ShowDebug = flag.Bool("debug", false, "Whether to print debug logs")
CleanupBaseResources = flag.Bool("cleanup-base-resources", true, "Whether to cleanup base test resources after the run")
SupportedFeatures = flag.String("supported-features", "", "Supported features included in conformance tests suites")
ExemptFeatures = flag.String("exempt-features", "", "Exempt Features excluded from conformance tests suites")
)
// FS embeds the manifests
//
//go:embed profiles/*
var FS embed.FS
// BuiltinOrDir returns a FS for the provided directory. If no directory is passed, the compiled in
// FS will be used
func BuiltinOrDir(dir string) fs.FS {
if dir == "" {
return FS
}
return os.DirFS(dir)
}

View File

@@ -0,0 +1,65 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
profile: kind
global:
install: local # install mode k8s/local/docker
ingressClass: higress
watchNamespace:
disableAlpnH2: true
enableStatus: true
enableIstioAPI: true
namespace: higress-system
istioNamespace: istio-system
console:
port: 8080
replicas: 1
serviceType: ClusterIP
domain: console.higress.io
tlsSecretName:
webLoginPrompt:
adminPasswordValue: admin
adminPasswordLength: 8
o11yEnabled: false
pvcRwxSupported: true
gateway:
replicas: 1
httpPort: 80
httpsPort: 443
metricsPort: 15020
controller:
replicas: 1
storage:
url: nacos://192.168.0.1:8848 # file://opt/higress/conf, buildin://127.0.0.1:8848
ns: higress-system
username:
password:
dataEncKey:
# values passed through to helm
values:
charts:
higress:
url: https://higress.io/helm-charts
name: higress
version: 1.1.2
istio:
url: https://istio-release.storage.googleapis.com/charts
name: base
version: 1.18.2

View File

@@ -0,0 +1,53 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
profile: k8s
global:
install: k8s # install mode k8s/local-k8s/local-docker/local
ingressClass: higress
watchNamespace:
disableAlpnH2: true
enableStatus: true
enableIstioAPI: false
namespace: higress-system
istioNamespace: istio-system
console:
replicas: 1
serviceType: ClusterIP
domain: console.higress.io
tlsSecretName:
webLoginPrompt:
adminPasswordValue: admin
adminPasswordLength: 8
o11yEnabled: false
pvcRwxSupported: true
gateway:
replicas: 2
controller:
replicas: 1
# values passed through to helm
values:
charts:
higress:
url: https://higress.io/helm-charts
name: higress
version: 1.1.2
istio:
url: https://istio-release.storage.googleapis.com/charts
name: base
version: 1.18.2

View File

@@ -0,0 +1,53 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
profile: local-k8s
global:
install: local-k8s # install mode k8s/local-k8s/local-docker/local
ingressClass: higress
watchNamespace:
disableAlpnH2: true
enableStatus: true
enableIstioAPI: true
namespace: higress-system
istioNamespace: istio-system
console:
replicas: 1
serviceType: ClusterIP
domain: console.higress.io
tlsSecretName:
webLoginPrompt:
adminPasswordValue: admin
adminPasswordLength: 8
o11yEnabled: true
pvcRwxSupported: true
gateway:
replicas: 1
controller:
replicas: 1
# values passed through to helm
values:
charts:
higress:
url: https://higress.io/helm-charts
name: higress
version: 1.1.2
istio:
url: https://istio-release.storage.googleapis.com/charts
name: base
version: 1.18.2

44
pkg/cmd/hgctl/profile.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
import (
"github.com/spf13/cobra"
)
// ProfileCmd is a group of commands related to profile listing, dumping and diffing.
func newProfileCmd() *cobra.Command {
pc := &cobra.Command{
Use: "profile",
Short: "Commands related to higress configuration profiles",
Long: "The profile command lists, dumps higress configuration profiles.",
Example: "hgctl profile list\n" +
"hgctl install --set profile=local-k8s # Use a profile from the list",
}
pdArgs := &profileDumpArgs{}
plArgs := &profileListArgs{}
plc := profileListCmd(plArgs)
pdc := profileDumpCmd(pdArgs)
addProfileDumpFlags(pdc, pdArgs)
addProfileListFlags(plc, plArgs)
pc.AddCommand(plc)
pc.AddCommand(pdc)
return pc
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
import (
"fmt"
"os"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/spf13/cobra"
)
type profileDumpArgs struct {
// output write profile to file
output string
// manifestsPath is a path to a charts and profiles directory in the local filesystem with a release tgz.
manifestsPath string
}
func addProfileDumpFlags(cmd *cobra.Command, args *profileDumpArgs) {
cmd.PersistentFlags().StringVarP(&args.output, "output", "o", "", outputHelpstr)
cmd.PersistentFlags().StringVarP(&args.manifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
}
func profileDumpCmd(pdArgs *profileDumpArgs) *cobra.Command {
return &cobra.Command{
Use: "dump [<profile>]",
Short: "Dumps a higress configuration profile",
Long: "The dump subcommand dumps the values in a higress configuration profile.",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 1 {
return fmt.Errorf("too many positional arguments")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return profileDump(cmd, args, pdArgs)
},
}
}
func profileDump(cmd *cobra.Command, args []string, pdArgs *profileDumpArgs) error {
profileName := helm.DefaultProfileName
if len(args) == 1 {
profileName = args[0]
}
yaml, err := helm.ReadProfileYAML(profileName, pdArgs.manifestsPath)
if err != nil {
return err
}
if len(pdArgs.output) > 0 {
err2 := os.WriteFile(pdArgs.output, []byte(yaml), 0644)
if err2 != nil {
return err2
}
} else {
cmd.Println(yaml)
}
return nil
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
import (
"sort"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/spf13/cobra"
)
type profileListArgs struct {
// manifestsPath is a path to a charts and profiles directory in the local filesystem with a release tgz.
manifestsPath string
}
func addProfileListFlags(cmd *cobra.Command, args *profileListArgs) {
cmd.PersistentFlags().StringVarP(&args.manifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
}
func profileListCmd(plArgs *profileListArgs) *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "Lists available higress configuration profiles",
Long: "The list subcommand lists the available higress configuration profiles.",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
return profileList(cmd, plArgs)
},
}
}
// profileList list all the builtin profiles.
func profileList(cmd *cobra.Command, plArgs *profileListArgs) error {
profiles, err := helm.ListProfiles(plArgs.manifestsPath)
if err != nil {
return err
}
if len(profiles) == 0 {
cmd.Println("No profiles available.")
} else {
cmd.Println("higress configuration profiles:")
sort.Strings(profiles)
for _, profile := range profiles {
cmd.Printf(" %s\n", profile)
}
}
return nil
}

View File

@@ -28,6 +28,12 @@ func GetRootCommand() *cobra.Command {
rootCmd.AddCommand(newVersionCommand())
rootCmd.AddCommand(newConfigCommand())
rootCmd.AddCommand(newInstallCmd())
rootCmd.AddCommand(newUninstallCmd())
rootCmd.AddCommand(newUpgradeCmd())
rootCmd.AddCommand(newProfileCmd())
rootCmd.AddCommand(newDashboardCmd())
rootCmd.AddCommand(newManifestCmd())
return rootCmd
}

136
pkg/cmd/hgctl/uninstall.go Normal file
View File

@@ -0,0 +1,136 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
import (
"fmt"
"io"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra"
)
type uninstallArgs struct {
// purgeIstioCRD delete all of Istio resources.
purgeIstioCRD bool
// istioNamespace is the target namespace of istio control plane.
istioNamespace string
// namespace is the namespace of higress installed .
namespace string
}
func addUninstallFlags(cmd *cobra.Command, args *uninstallArgs) {
cmd.PersistentFlags().StringVar(&args.istioNamespace, "istio-namespace", "istio-system",
"The namespace of Istio Control Plane.")
cmd.PersistentFlags().StringVarP(&args.namespace, "namespace", "n", "higress-system",
"The namespace of higress")
cmd.PersistentFlags().BoolVarP(&args.purgeIstioCRD, "purge-istio-crd", "p", false,
"Delete all of Istio resources")
}
// newUninstallCmd command uninstalls Istio from a cluster
func newUninstallCmd() *cobra.Command {
uiArgs := &uninstallArgs{}
uninstallCmd := &cobra.Command{
Use: "uninstall",
Short: "Uninstall higress from a cluster",
Long: "The uninstall command uninstalls higress from a cluster",
Example: ` # Uninstall higress
hgctl uninstall
# Uninstall higress by special namespace
hgctl uninstall --namespace=higress-system
# Uninstall higress and istio CRD
hgctl uninstall --purge-istio-crd --istio-namespace=istio-system`,
RunE: func(cmd *cobra.Command, args []string) error {
return uninstall(cmd.OutOrStdout(), uiArgs)
},
}
addUninstallFlags(uninstallCmd, uiArgs)
flags := uninstallCmd.Flags()
options.AddKubeConfigFlags(flags)
return uninstallCmd
}
// uninstall uninstalls control plane by either pruning by target revision or deleting specified manifests.
func uninstall(writer io.Writer, uiArgs *uninstallArgs) error {
setFlags := make([]string, 0)
profileName := helm.GetUninstallProfileName()
_, profile, err := helm.GenProfile(profileName, "", setFlags)
if err != nil {
return err
}
if !promptUninstall(writer) {
return nil
}
profile.Global.EnableIstioAPI = uiArgs.purgeIstioCRD
profile.Global.Namespace = uiArgs.namespace
profile.Global.IstioNamespace = uiArgs.istioNamespace
err = UnInstallManifests(profile, writer)
if err != nil {
return err
}
return nil
}
func promptUninstall(writer io.Writer) bool {
answer := ""
for {
fmt.Fprintf(writer, "All Higress resources will be uninstalled from the cluster. \nProceed? (y/N)")
fmt.Scanln(&answer)
if strings.TrimSpace(answer) == "y" {
fmt.Fprintf(writer, "\n")
return true
}
if strings.TrimSpace(answer) == "N" {
fmt.Fprintf(writer, "Cancelled.\n")
return false
}
}
}
func UnInstallManifests(profile *helm.Profile, writer io.Writer) error {
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("failed to build kubernetes client: %w", err)
}
op, err := installer.NewInstaller(profile, cliClient, writer, false)
if err != nil {
return err
}
if err := op.Run(); err != nil {
return err
}
manifestMap, err := op.RenderManifests()
if err != nil {
return err
}
fmt.Fprintf(writer, "\n⌛ Processing uninstallation... \n\n")
if err := op.DeleteManifests(manifestMap); err != nil {
return err
}
fmt.Fprintf(writer, "\n🎊 Uninstall All Resources Complete!\n")
return nil
}

49
pkg/cmd/hgctl/upgrade.go Normal file
View File

@@ -0,0 +1,49 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hgctl
import (
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra"
)
type upgradeArgs struct {
*InstallArgs
}
func addUpgradeFlags(cmd *cobra.Command, args *upgradeArgs) {
cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr)
cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr)
}
// newUpgradeCmd upgrades Istio control plane in-place with eligibility checks.
func newUpgradeCmd() *cobra.Command {
upgradeArgs := &upgradeArgs{
InstallArgs: &InstallArgs{},
}
upgradeCmd := &cobra.Command{
Use: "upgrade",
Short: "Upgrade Higress in-place",
Long: "The upgrade command is an alias for the install command" +
" that performs additional upgrade-related checks.",
RunE: func(cmd *cobra.Command, args []string) (e error) {
return Install(cmd.OutOrStdout(), upgradeArgs.InstallArgs)
},
}
addUpgradeFlags(upgradeCmd, upgradeArgs)
flags := upgradeCmd.Flags()
options.AddKubeConfigFlags(flags)
return upgradeCmd
}

View File

@@ -0,0 +1,91 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"bufio"
"io"
"strings"
"github.com/google/yamlfmt/formatters/basic"
)
var (
formatterConfig = func() *basic.Config {
cfg := basic.DefaultConfig()
return cfg
}()
formatter = &basic.BasicFormatter{
Config: formatterConfig,
Features: basic.ConfigureFeaturesFromConfig(formatterConfig),
}
)
// FilterFunc is used to filter some contents of manifest.
type FilterFunc func(string) string
func ApplyFilters(input string, filters ...FilterFunc) string {
for _, filter := range filters {
input = filter(input)
}
return input
}
// LicenseFilter assumes that license is at the beginning.
// So we just remove all the leading comments until the first non-comment line appears.
func LicenseFilter(input string) string {
var index int
buf := bufio.NewReader(strings.NewReader(input))
for {
line, err := buf.ReadString('\n')
if !strings.HasPrefix(line, "#") {
return input[index:]
}
index += len(line)
if err == io.EOF {
return input[index:]
}
}
}
// SpaceFilter removes all leading and trailing space.
func SpaceFilter(input string) string {
return strings.TrimSpace(input)
}
// SpaceLineFilter removes all space lines.
func SpaceLineFilter(input string) string {
var builder strings.Builder
scanner := bufio.NewScanner(strings.NewReader(input))
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) == "" {
continue
}
builder.WriteString(line)
builder.WriteString("\n")
}
return builder.String()
}
// FormatterFilter uses github.com/google/yamlfmt to format yaml file
func FormatterFilter(input string) string {
resBytes, err := formatter.Format([]byte(input))
// todo: think about log
if err != nil {
return input
}
return string(resBytes)
}

View File

@@ -0,0 +1,98 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"testing"
)
func TestLicenseFilter(t *testing.T) {
tests := []struct {
input string
want string
}{
{
input: `# license line
content line`,
want: `content line`,
},
{
input: `# license line`,
want: "",
},
{
input: `# license line
content line
# comment line`,
want: `content line
# comment line`,
},
}
for _, test := range tests {
res := LicenseFilter(test.input)
if res != test.want {
t.Errorf("want %s\n but got %s", test.want, res)
}
}
}
func TestSpaceFilter(t *testing.T) {
tests := []struct {
input string
want string
}{
{
input: `
content line
`,
want: "content line",
},
}
for _, test := range tests {
res := SpaceFilter(test.input)
if res != test.want {
t.Errorf("want %s\n but got %s", test.want, res)
}
}
}
func TestFormatterFilter(t *testing.T) {
tests := []struct {
input string
want string
}{
{
input: `key1: val1 `,
want: `key1: val1
`,
},
{
input: `key1:
key2: val2`,
want: `key1:
key2: val2
`,
},
}
for _, test := range tests {
res := FormatterFilter(test.input)
if res != test.want {
t.Errorf("want \n%s\n but got \n%s\n", test.want, res)
}
}
}

209
pkg/cmd/hgctl/util/path.go Normal file
View File

@@ -0,0 +1,209 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
)
const (
// PathSeparator is the separator between path elements.
PathSeparator = "."
// KVSeparator is the separator between the key and value in a key/value path element,
KVSeparator = string(kvSeparatorRune)
kvSeparatorRune = ':'
// InsertIndex is the index that means "insert" when setting values
InsertIndex = -1
// PathSeparatorRune is the separator between path elements, as a rune.
pathSeparatorRune = '.'
// EscapedPathSeparator is what to use when the path shouldn't separate
EscapedPathSeparator = "\\" + PathSeparator
)
// ValidKeyRegex is a regex for a valid path key element.
var ValidKeyRegex = regexp.MustCompile("^[a-zA-Z0-9_-]*$")
// Path is a path in slice form.
type Path []string
// PathFromString converts a string path of form a.b.c to a string slice representation.
func PathFromString(path string) Path {
path = filepath.Clean(path)
path = strings.TrimPrefix(path, PathSeparator)
path = strings.TrimSuffix(path, PathSeparator)
pv := splitEscaped(path, pathSeparatorRune)
var r []string
for _, str := range pv {
if str != "" {
str = strings.ReplaceAll(str, EscapedPathSeparator, PathSeparator)
// Is str of the form node[expr], convert to "node", "[expr]"?
nBracket := strings.IndexRune(str, '[')
if nBracket > 0 {
r = append(r, str[:nBracket], str[nBracket:])
} else {
// str is "[expr]" or "node"
r = append(r, str)
}
}
}
return r
}
// String converts a string slice path representation of form ["a", "b", "c"] to a string representation like "a.b.c".
func (p Path) String() string {
return strings.Join(p, PathSeparator)
}
func (p Path) Equals(p2 Path) bool {
if len(p) != len(p2) {
return false
}
for i, pp := range p {
if pp != p2[i] {
return false
}
}
return true
}
// ToYAMLPath converts a path string to path such that the first letter of each path element is lower case.
func ToYAMLPath(path string) Path {
p := PathFromString(path)
for i := range p {
p[i] = firstCharToLowerCase(p[i])
}
return p
}
// ToYAMLPathString converts a path string such that the first letter of each path element is lower case.
func ToYAMLPathString(path string) string {
return ToYAMLPath(path).String()
}
// IsValidPathElement reports whether pe is a valid path element.
func IsValidPathElement(pe string) bool {
return ValidKeyRegex.MatchString(pe)
}
// IsKVPathElement report whether pe is a key/value path element.
func IsKVPathElement(pe string) bool {
pe, ok := RemoveBrackets(pe)
if !ok {
return false
}
kv := splitEscaped(pe, kvSeparatorRune)
if len(kv) != 2 || len(kv[0]) == 0 || len(kv[1]) == 0 {
return false
}
return IsValidPathElement(kv[0])
}
// IsVPathElement report whether pe is a value path element.
func IsVPathElement(pe string) bool {
pe, ok := RemoveBrackets(pe)
if !ok {
return false
}
return len(pe) > 1 && pe[0] == ':'
}
// IsNPathElement report whether pe is an index path element.
func IsNPathElement(pe string) bool {
pe, ok := RemoveBrackets(pe)
if !ok {
return false
}
n, err := strconv.Atoi(pe)
return err == nil && n >= InsertIndex
}
// PathKV returns the key and value string parts of the entire key/value path element.
// It returns an error if pe is not a key/value path element.
func PathKV(pe string) (k, v string, err error) {
if !IsKVPathElement(pe) {
return "", "", fmt.Errorf("%s is not a valid key:value path element", pe)
}
pe, _ = RemoveBrackets(pe)
kv := splitEscaped(pe, kvSeparatorRune)
return kv[0], kv[1], nil
}
// PathV returns the value string part of the entire value path element.
// It returns an error if pe is not a value path element.
func PathV(pe string) (string, error) {
// For :val, return the value only
if IsVPathElement(pe) {
v, _ := RemoveBrackets(pe)
return v[1:], nil
}
// For key:val, return the whole thing
v, _ := RemoveBrackets(pe)
if len(v) > 0 {
return v, nil
}
return "", fmt.Errorf("%s is not a valid value path element", pe)
}
// PathN returns the index part of the entire value path element.
// It returns an error if pe is not an index path element.
func PathN(pe string) (int, error) {
if !IsNPathElement(pe) {
return -1, fmt.Errorf("%s is not a valid index path element", pe)
}
v, _ := RemoveBrackets(pe)
return strconv.Atoi(v)
}
// RemoveBrackets removes the [] around pe and returns the resulting string. It returns false if pe is not surrounded
// by [].
func RemoveBrackets(pe string) (string, bool) {
if !strings.HasPrefix(pe, "[") || !strings.HasSuffix(pe, "]") {
return "", false
}
return pe[1 : len(pe)-1], true
}
// splitEscaped splits a string using the rune r as a separator. It does not split on r if it's prefixed by \.
func splitEscaped(s string, r rune) []string {
var prev rune
if len(s) == 0 {
return []string{}
}
prevIdx := 0
var out []string
for i, c := range s {
if c == r && (i == 0 || (i > 0 && prev != '\\')) {
out = append(out, s[prevIdx:i])
prevIdx = i + 1
}
prev = c
}
out = append(out, s[prevIdx:])
return out
}
func firstCharToLowerCase(s string) string {
return strings.ToLower(s[0:1]) + s[1:]
}

View File

@@ -0,0 +1,383 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"errors"
"testing"
)
func TestSplitEscaped(t *testing.T) {
tests := []struct {
desc string
in string
want []string
}{
{
desc: "empty",
in: "",
want: []string{},
},
{
desc: "no match",
in: "foo",
want: []string{"foo"},
},
{
desc: "first",
in: ":foo",
want: []string{"", "foo"},
},
{
desc: "last",
in: "foo:",
want: []string{"foo", ""},
},
{
desc: "multiple",
in: "foo:bar:baz",
want: []string{"foo", "bar", "baz"},
},
{
desc: "multiple with escapes",
in: `foo\:bar:baz\:qux`,
want: []string{`foo\:bar`, `baz\:qux`},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got, want := splitEscaped(tt.in, kvSeparatorRune), tt.want; !stringSlicesEqual(got, want) {
t.Errorf("%s: got:%v, want:%v", tt.desc, got, want)
}
})
}
}
func TestIsNPathElement(t *testing.T) {
tests := []struct {
desc string
in string
expect bool
}{
{
desc: "empty",
in: "",
expect: false,
},
{
desc: "negative",
in: "[-45]",
expect: false,
},
{
desc: "negative-1",
in: "[-1]",
expect: true,
},
{
desc: "valid",
in: "[0]",
expect: true,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got := IsNPathElement(tt.in); got != tt.expect {
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
}
})
}
}
func stringSlicesEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, aa := range a {
if aa != b[i] {
return false
}
}
return true
}
func TestPathFromString(t *testing.T) {
tests := []struct {
desc string
in string
expect Path
}{
{
desc: "no-path",
in: "",
expect: Path{},
},
{
desc: "valid-path",
in: "a.b.c",
expect: Path{"a", "b", "c"},
},
{
desc: "surround-periods",
in: ".a.",
expect: Path{"a"},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got := PathFromString(tt.in); !got.Equals(tt.expect) {
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
}
})
}
}
func TestToYAMLPath(t *testing.T) {
tests := []struct {
desc string
in string
expect Path
}{
{
desc: "all-uppercase",
in: "A.B.C.D",
expect: Path{"a", "b", "c", "d"},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got := ToYAMLPath(tt.in); !got.Equals(tt.expect) {
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
}
})
}
}
func TestIsKVPathElement(t *testing.T) {
tests := []struct {
desc string
in string
expect bool
}{
{
desc: "valid",
in: "[1:2]",
expect: true,
},
{
desc: "invalid",
in: "[:2]",
expect: false,
},
{
desc: "invalid-2",
in: "[1:]",
expect: false,
},
{
desc: "empty",
in: "",
expect: false,
},
{
desc: "no-brackets",
in: "1:2",
expect: false,
},
{
desc: "one-bracket",
in: "[1:2",
expect: false,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got := IsKVPathElement(tt.in); got != tt.expect {
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
}
})
}
}
func TestIsVPathElement(t *testing.T) {
tests := []struct {
desc string
in string
expect bool
}{
{
desc: "valid",
in: "[:1]",
expect: true,
},
{
desc: "kv-path-elem",
in: "[1:2]",
expect: false,
},
{
desc: "invalid",
in: "1:2",
expect: false,
},
{
desc: "empty",
in: "",
expect: false,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got := IsVPathElement(tt.in); got != tt.expect {
t.Errorf("%s: expect %v got %v", tt.desc, tt.expect, got)
}
})
}
}
func TestPathKV(t *testing.T) {
tests := []struct {
desc string
in string
wantK string
wantV string
wantErr error
}{
{
desc: "valid",
in: "[1:2]",
wantK: "1",
wantV: "2",
wantErr: nil,
},
{
desc: "invalid",
in: "[1:",
wantErr: errors.New(""),
},
{
desc: "empty",
in: "",
wantErr: errors.New(""),
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if k, v, err := PathKV(tt.in); k != tt.wantK || v != tt.wantV || errNilCheck(err, tt.wantErr) {
t.Errorf("%s: expect %v %v %v got %v %v %v", tt.desc, tt.wantK, tt.wantV, tt.wantErr, k, v, err)
}
})
}
}
func TestPathV(t *testing.T) {
tests := []struct {
desc string
in string
want string
err error
}{
{
desc: "valid-kv",
in: "[1:2]",
want: "1:2",
err: nil,
},
{
desc: "valid-v",
in: "[:1]",
want: "1",
err: nil,
},
{
desc: "invalid",
in: "083fj",
want: "",
err: errors.New(""),
},
{
desc: "empty",
in: "",
want: "",
err: errors.New(""),
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got, err := PathV(tt.in); got != tt.want || errNilCheck(err, tt.err) {
t.Errorf("%s: expect %v %v got %v %v", tt.desc, tt.want, tt.err, got, err)
}
})
}
}
func TestRemoveBrackets(t *testing.T) {
tests := []struct {
desc string
in string
expect string
expectStat bool
}{
{
desc: "has-brackets",
in: "[yo]",
expect: "yo",
expectStat: true,
},
{
desc: "one-bracket",
in: "[yo",
expect: "",
expectStat: false,
},
{
desc: "other-bracket",
in: "yo]",
expect: "",
expectStat: false,
},
{
desc: "no-brackets",
in: "yo",
expect: "",
expectStat: false,
},
{
desc: "empty",
in: "",
expect: "",
expectStat: false,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
if got, stat := RemoveBrackets(tt.in); got != tt.expect || stat != tt.expectStat {
t.Errorf("%s: expect %v %v got %v %v", tt.desc, tt.expect, tt.expectStat, got, stat)
}
})
}
}
func errNilCheck(err1, err2 error) bool {
return (err1 == nil && err2 != nil) || (err1 != nil && err2 == nil)
}

View File

@@ -0,0 +1,311 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"fmt"
"reflect"
)
// kindOf returns the reflection Kind that represents the dynamic type of value.
// If value is a nil interface value, kindOf returns reflect.Invalid.
func kindOf(value any) reflect.Kind {
if value == nil {
return reflect.Invalid
}
return reflect.TypeOf(value).Kind()
}
// IsString reports whether value is a string type.
func IsString(value any) bool {
return kindOf(value) == reflect.String
}
// IsPtr reports whether value is a ptr type.
func IsPtr(value any) bool {
return kindOf(value) == reflect.Ptr
}
// IsMap reports whether value is a map type.
func IsMap(value any) bool {
return kindOf(value) == reflect.Map
}
// IsMapPtr reports whether v is a map ptr type.
func IsMapPtr(v any) bool {
t := reflect.TypeOf(v)
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Map
}
// IsSlice reports whether value is a slice type.
func IsSlice(value any) bool {
return kindOf(value) == reflect.Slice
}
// IsStruct reports whether value is a struct type
func IsStruct(value any) bool {
return kindOf(value) == reflect.Struct
}
// IsSlicePtr reports whether v is a slice ptr type.
func IsSlicePtr(v any) bool {
return kindOf(v) == reflect.Ptr && reflect.TypeOf(v).Elem().Kind() == reflect.Slice
}
// IsSliceInterfacePtr reports whether v is a slice ptr type.
func IsSliceInterfacePtr(v any) bool {
// Must use ValueOf because Elem().Elem() type resolves dynamically.
vv := reflect.ValueOf(v)
return vv.Kind() == reflect.Ptr && vv.Elem().Kind() == reflect.Interface && vv.Elem().Elem().Kind() == reflect.Slice
}
// IsTypeStructPtr reports whether v is a struct ptr type.
func IsTypeStructPtr(t reflect.Type) bool {
if t == reflect.TypeOf(nil) {
return false
}
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}
// IsTypeSlicePtr reports whether v is a slice ptr type.
func IsTypeSlicePtr(t reflect.Type) bool {
if t == reflect.TypeOf(nil) {
return false
}
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Slice
}
// IsTypeMap reports whether v is a map type.
func IsTypeMap(t reflect.Type) bool {
if t == reflect.TypeOf(nil) {
return false
}
return t.Kind() == reflect.Map
}
// IsTypeInterface reports whether v is an interface.
func IsTypeInterface(t reflect.Type) bool {
if t == reflect.TypeOf(nil) {
return false
}
return t.Kind() == reflect.Interface
}
// IsTypeSliceOfInterface reports whether v is a slice of interface.
func IsTypeSliceOfInterface(t reflect.Type) bool {
if t == reflect.TypeOf(nil) {
return false
}
return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Interface
}
// IsNilOrInvalidValue reports whether v is nil or reflect.Zero.
func IsNilOrInvalidValue(v reflect.Value) bool {
return !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) || IsValueNil(v.Interface())
}
// IsValueNil returns true if either value is nil, or has dynamic type {ptr,
// map, slice} with value nil.
func IsValueNil(value any) bool {
if value == nil {
return true
}
switch kindOf(value) {
case reflect.Slice, reflect.Ptr, reflect.Map:
return reflect.ValueOf(value).IsNil()
}
return false
}
// IsValueNilOrDefault returns true if either IsValueNil(value) or the default
// value for the type.
func IsValueNilOrDefault(value any) bool {
if IsValueNil(value) {
return true
}
if !IsValueScalar(reflect.ValueOf(value)) {
// Default value is nil for non-scalar types.
return false
}
return value == reflect.New(reflect.TypeOf(value)).Elem().Interface()
}
// IsValuePtr reports whether v is a ptr type.
func IsValuePtr(v reflect.Value) bool {
return v.Kind() == reflect.Ptr
}
// IsValueInterface reports whether v is an interface type.
func IsValueInterface(v reflect.Value) bool {
return v.Kind() == reflect.Interface
}
// IsValueStruct reports whether v is a struct type.
func IsValueStruct(v reflect.Value) bool {
return v.Kind() == reflect.Struct
}
// IsValueStructPtr reports whether v is a struct ptr type.
func IsValueStructPtr(v reflect.Value) bool {
return v.Kind() == reflect.Ptr && IsValueStruct(v.Elem())
}
// IsValueMap reports whether v is a map type.
func IsValueMap(v reflect.Value) bool {
return v.Kind() == reflect.Map
}
// IsValueSlice reports whether v is a slice type.
func IsValueSlice(v reflect.Value) bool {
return v.Kind() == reflect.Slice
}
// IsValueScalar reports whether v is a scalar type.
func IsValueScalar(v reflect.Value) bool {
if IsNilOrInvalidValue(v) {
return false
}
if IsValuePtr(v) {
if v.IsNil() {
return false
}
v = v.Elem()
}
return !IsValueStruct(v) && !IsValueMap(v) && !IsValueSlice(v)
}
// ValuesAreSameType returns true if v1 and v2 has the same reflect.Type,
// otherwise it returns false.
func ValuesAreSameType(v1 reflect.Value, v2 reflect.Value) bool {
return v1.Type() == v2.Type()
}
// IsEmptyString returns true if value is an empty string.
func IsEmptyString(value any) bool {
if value == nil {
return true
}
switch kindOf(value) {
case reflect.String:
if _, ok := value.(string); ok {
return value.(string) == ""
}
}
return false
}
// DeleteFromSlicePtr deletes an entry at index from the parent, which must be a slice ptr.
func DeleteFromSlicePtr(parentSlice any, index int) error {
pv := reflect.ValueOf(parentSlice)
if !IsSliceInterfacePtr(parentSlice) {
return fmt.Errorf("deleteFromSlicePtr parent type is %T, must be *[]interface{}", parentSlice)
}
pvv := pv.Elem()
if pvv.Kind() == reflect.Interface {
pvv = pvv.Elem()
}
pv.Elem().Set(reflect.AppendSlice(pvv.Slice(0, index), pvv.Slice(index+1, pvv.Len())))
return nil
}
// UpdateSlicePtr updates an entry at index in the parent, which must be a slice ptr, with the given value.
func UpdateSlicePtr(parentSlice any, index int, value any) error {
pv := reflect.ValueOf(parentSlice)
v := reflect.ValueOf(value)
if !IsSliceInterfacePtr(parentSlice) {
return fmt.Errorf("updateSlicePtr parent type is %T, must be *[]interface{}", parentSlice)
}
pvv := pv.Elem()
if pvv.Kind() == reflect.Interface {
pv.Elem().Elem().Index(index).Set(v)
return nil
}
pv.Elem().Index(index).Set(v)
return nil
}
// InsertIntoMap inserts value with key into parent which must be a map, map ptr, or interface to map.
func InsertIntoMap(parentMap any, key any, value any) error {
v := reflect.ValueOf(parentMap)
kv := reflect.ValueOf(key)
vv := reflect.ValueOf(value)
if v.Type().Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Type().Kind() == reflect.Interface {
v = v.Elem()
}
if v.Type().Kind() != reflect.Map {
return fmt.Errorf("insertIntoMap parent type is %T, must be map", parentMap)
}
v.SetMapIndex(kv, vv)
return nil
}
// DeleteFromMap deletes an entry with the given key parent, which must be a map.
func DeleteFromMap(parentMap any, key any) error {
pv := reflect.ValueOf(parentMap)
if !IsMap(parentMap) {
return fmt.Errorf("deleteFromMap parent type is %T, must be map", parentMap)
}
pv.SetMapIndex(reflect.ValueOf(key), reflect.Value{})
return nil
}
// ToIntValue returns 0, false if val is not a number type, otherwise it returns the int value of val.
func ToIntValue(val any) (int, bool) {
if IsValueNil(val) {
return 0, false
}
v := reflect.ValueOf(val)
switch {
case IsIntKind(v.Kind()):
return int(v.Int()), true
case IsUintKind(v.Kind()):
return int(v.Uint()), true
}
return 0, false
}
// IsIntKind reports whether k is an integer kind of any size.
func IsIntKind(k reflect.Kind) bool {
switch k {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
}
return false
}
// IsUintKind reports whether k is an unsigned integer kind of any size.
func IsUintKind(k reflect.Kind) bool {
switch k {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return true
}
return false
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"fmt"
"net/url"
"strconv"
"strings"
)
// StripPrefix removes the given prefix from prefix.
func StripPrefix(path, prefix string) string {
pl := len(strings.Split(prefix, "/"))
pv := strings.Split(path, "/")
return strings.Join(pv[pl:], "/")
}
func SplitSetFlag(flag string) (string, string) {
items := strings.Split(flag, "=")
if len(items) != 2 {
return flag, ""
}
return strings.TrimSpace(items[0]), strings.TrimSpace(items[1])
}
// IsFilePath reports whether the given URL is a local file path.
func IsFilePath(path string) bool {
return strings.Contains(path, "/") || strings.Contains(path, ".")
}
// IsHTTPURL checks whether the given URL is a HTTP URL.
func IsHTTPURL(path string) (bool, error) {
u, err := url.Parse(path)
valid := err == nil && u.Host != "" && (u.Scheme == "http" || u.Scheme == "https")
if strings.HasPrefix(path, "http") && !valid {
return false, fmt.Errorf("%s starts with http but is not a valid URL: %s", path, err)
}
return valid, nil
}
// StringBoolMapToSlice creates and returns a slice of all the map keys with true.
func StringBoolMapToSlice(m map[string]bool) []string {
s := make([]string, 0, len(m))
for k, v := range m {
if v {
s = append(s, k)
}
}
return s
}
// ParseValue parses string into a value
func ParseValue(valueStr string) any {
var value any
if v, err := strconv.Atoi(valueStr); err == nil {
value = v
} else if v, err := strconv.ParseFloat(valueStr, 64); err == nil {
value = v
} else if v, err := strconv.ParseBool(valueStr); err == nil {
value = v
} else {
value = strings.ReplaceAll(valueStr, "\\,", ",")
}
return value
}

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