This is an automated email from the ASF dual-hosted git repository.

zhongxjian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git


The following commit(s) were added to refs/heads/master by this push:
     new bf01489b Update makefile and client-go (#842)
bf01489b is described below

commit bf01489b62850055110b7fb5e2a9e0d0051d3f26
Author: mfordjody <[email protected]>
AuthorDate: Tue Jan 6 03:40:19 2026 +0800

    Update makefile and client-go (#842)
---
 Makefile                                           |  49 +-
 Makefile.common.mk                                 |  13 +
 Makefile.core.mk                                   |  85 +++
 Makefile.overrides.mk                              |   1 +
 api/networking/v1alpha3/destination_rule.pb.go     |  16 +
 api/networking/v1alpha3/destination_rule.proto     |  15 +
 api/networking/v1alpha3/virtual_service.pb.go      |  16 +
 api/networking/v1alpha3/virtual_service.proto      |  15 +
 client-go/pkg/apis/networking/v1alpha3/doc.go      |  23 +
 client-go/pkg/apis/networking/v1alpha3/register.go |  53 ++
 client-go/pkg/apis/networking/v1alpha3/types.go    | 119 ++++
 .../pkg/applyconfiguration/internal/internal.go    |  62 ++
 .../pkg/applyconfiguration/meta/v1/objectmeta.go   | 172 +++++
 .../applyconfiguration/meta/v1/ownerreference.go   |  88 +++
 .../pkg/applyconfiguration/meta/v1/typemeta.go     |  48 ++
 client-go/pkg/applyconfiguration/utils.go          |  41 ++
 client-go/pkg/clientset/versioned/clientset.go     |  19 +
 .../versioned/fake/clientset_generated.go          |  19 +
 client-go/pkg/clientset/versioned/fake/doc.go      |  20 +
 client-go/pkg/clientset/versioned/fake/register.go |  19 +
 client-go/pkg/clientset/versioned/scheme/doc.go    |  20 +
 .../pkg/clientset/versioned/scheme/register.go     |  19 +
 .../versioned/typed/networking/v1alpha3/doc.go     |  20 +
 .../typed/networking/v1alpha3/fake/doc.go          |  20 +
 .../v1alpha3/fake/fake_networking_client.go        |  19 +
 .../networking/v1alpha3/generated_expansion.go     |  19 +
 .../typed/networking/v1alpha3/networking_client.go |  19 +
 go.mod                                             |   9 +-
 go.sum                                             |   5 +
 header.go.txt                                      |  16 +
 kubetype-gen                                       | Bin 11522162 -> 0 bytes
 tests/grpc-app/.dockerignore                       |  21 -
 tests/grpc-app/.gitignore                          |   2 -
 tests/grpc-app/README.md                           |  12 -
 tests/grpc-app/consumer/main.go                    | 739 ---------------------
 tests/grpc-app/docker/dockerfile.consumer          |  53 --
 tests/grpc-app/docker/dockerfile.provider          |  50 --
 tests/grpc-app/generate-proto.sh                   |  27 -
 tests/grpc-app/go.mod                              |  44 --
 tests/grpc-app/go.sum                              |  76 ---
 tests/grpc-app/proto/echo.proto                    |  62 --
 tests/grpc-app/proto/gen.sh                        |   7 -
 tests/grpc-app/provider/main.go                    | 404 -----------
 tests/loadtest/go.mod                              |  29 -
 tests/loadtest/go.sum                              |  85 ---
 tools/scripts/run.sh                               |  43 ++
 tools/scripts/setup_env.sh                         | 145 ++++
 47 files changed, 1243 insertions(+), 1615 deletions(-)

diff --git a/Makefile b/Makefile
index 39b62ecf..ecb80294 100644
--- a/Makefile
+++ b/Makefile
@@ -22,4 +22,51 @@ build-dubboctl:
 .PHONY: clone-sample
 clone-sample:
        mkdir -p bin
-       cp -r samples bin/samples
\ No newline at end of file
+       cp -r samples bin/samples
+
+# allow optional per-repo overrides
+-include Makefile.overrides.mk
+
+# Set the environment variable BUILD_WITH_CONTAINER to use a container
+# to build the repo. The only dependencies in this mode are to have make and
+# docker. If you'd rather build with a local tool chain instead, you'll need to
+# figure out all the tools you need in your environment to make that work.
+export BUILD_WITH_CONTAINER ?= 0
+
+ifeq ($(BUILD_WITH_CONTAINER),1)
+
+# An export free of arguments in a Makefile places all variables in the 
Makefile into the
+# environment. This is needed to allow overrides from Makefile.overrides.mk.
+export
+
+RUN = ./tools/scripts/run.sh
+
+MAKE_DOCKER = $(RUN) make --no-print-directory -e -f Makefile.core.mk
+
+%:
+       @$(MAKE_DOCKER) $@
+
+default:
+       @$(MAKE_DOCKER)
+
+shell:
+       @$(RUN) /bin/bash
+
+.PHONY: default shell
+
+else
+
+# If we are not in build container, we need a workaround to get environment 
properly set
+# Write to file, then include
+$(shell mkdir -p out)
+$(shell $(shell pwd)/tools/scripts/setup_env.sh envfile > out/.env)
+include out/.env
+# An export free of arguments in a Makefile places all variables in the 
Makefile into the
+# environment. This behavior may be surprising to many that use shell often, 
which simply
+# displays the existing environment
+export
+
+export GOBIN ?= $(GOPATH)/bin
+include Makefile.core.mk
+
+endif
\ No newline at end of file
diff --git a/Makefile.common.mk b/Makefile.common.mk
new file mode 100644
index 00000000..d4f4d53b
--- /dev/null
+++ b/Makefile.common.mk
@@ -0,0 +1,13 @@
+tidy-go:
+       @find -name go.mod -execdir go mod tidy \;
+
+mod-download-go:
+       @-GOFLAGS="-mod=readonly" find -name go.mod -execdir go mod download \;
+# go mod tidy is needed with Golang 1.16+ as go mod download affects go.sum
+# https://github.com/golang/go/issues/43994
+       @find -name go.mod -execdir go mod tidy \;
+
+format-go: tidy-go
+       @${FINDFILES} -name '*.go' \( ! \( -name '*.gen.go' -o -name '*.pb.go' 
\) \) -print0 | ${XARGS} common/scripts/format_go.sh
+
+.PHONY: format-go tidy-go mod-download-go
diff --git a/Makefile.core.mk b/Makefile.core.mk
new file mode 100644
index 00000000..49c252b6
--- /dev/null
+++ b/Makefile.core.mk
@@ -0,0 +1,85 @@
+gen: generate-k8s-client tidy-go
+
+clean: clean-k8s-client
+
+applyconfiguration_gen = applyconfiguration-gen
+kubetype_gen = kubetype-gen
+deepcopy_gen = deepcopy-gen
+client_gen = client-gen
+lister_gen = lister-gen
+informer_gen = informer-gen
+
+empty:=
+space := $(empty) $(empty)
+comma := ,
+
+kube_dubbo_source_packages = $(subst $(space),$(empty), \
+       ./api/networking/v1alpha3 \
+       )
+
+kube_base_output_package = client-go/pkg
+kube_api_base_package = $(kube_base_output_package)/apis
+kube_api_packages = $(subst $(space),$(empty), \
+       $(kube_api_base_package)/networking/v1alpha3 \
+       )
+
+kube_api_applyconfiguration_packages = 
$(kube_api_packages),k8s.io/apimachinery/pkg/apis/meta/v1
+kube_clientset_package = $(kube_base_output_package)/clientset
+kube_clientset_name = versioned
+kube_listers_package = $(kube_base_output_package)/listers
+kube_informers_package = $(kube_base_output_package)/informers
+kube_applyconfiguration_package = 
$(kube_base_output_package)/applyconfiguration
+
+kube_go_header_text = header.go.txt
+
+ifeq ($(IN_BUILD_CONTAINER),1)
+       # k8s code generators rely on GOPATH, using $GOPATH/src as the base 
package
+       # directory.  Using --output-base . does not work, as that ends up 
generating
+       # code into ./<package>, e.g. ./client-go/pkg/apis/...  To work
+       # around this, we'll just let k8s generate the code where it wants and 
copy
+       # back to where it should have been generated.
+       move_generated=([ -d $(GOPATH)/src/$(kube_base_output_package)/ ] && cp 
-r $(GOPATH)/src/$(kube_base_output_package)/ client-go/ && rm -rf 
$(GOPATH)/src/$(kube_base_output_package)/) || true
+else
+       # nothing special for local builds
+       move_generated=
+endif
+
+rename_generated_files=\
+       for dir in $(subst client-go/, $(empty), $(subst $(comma), $(space), 
$(kube_api_packages)) $(kube_clientset_package) $(kube_listers_package) 
$(kube_informers_package)); do \
+               if [ -d "$$dir" ]; then \
+                       find "$$dir" -name '*.go' -and -not -name 'doc.go' -and 
-not -name '*.gen.go' -type f -exec sh -c 'mv "$$1" "$${1%.go}".gen.go' - '{}' 
\; ; \
+               fi \
+       done
+
+
+# Kubernetes deepcopy gen directly sets values of our types. Our types are 
protos; it is illegal to do this for protos.
+# However, we don't even need this anyways -- each individual field is 
explicitly copied already.
+# Remove the line doing this illegal operation.
+fixup_generated_files=\
+       find . -name "*.deepcopy.gen.go" -type f -exec sed -i '' -e '/\*out = 
\*in/d' {} +
+
+.PHONY: generate-k8s-client
+generate-k8s-client:
+       # generate kube api type wrappers for dubbo types
+       @GODEBUG=gotypesalias=0 $(kubetype_gen) --input-dirs 
$(kube_dubbo_source_packages) --output-package $(kube_api_base_package) -h 
$(kube_go_header_text)
+       @$(move_generated)
+       # generate deepcopy for kube api types
+       @$(deepcopy_gen) --input-dirs $(kube_api_packages) -O 
zz_generated.deepcopy  -h $(kube_go_header_text)
+       # generate ssa for kube api types
+       @$(applyconfiguration_gen) --input-dirs 
$(kube_api_applyconfiguration_packages) --output-package 
$(kube_applyconfiguration_package) -h $(kube_go_header_text)
+       # generate clientsets for kube api types
+       @$(client_gen) --clientset-name $(kube_clientset_name) --input-base "" 
--input  $(kube_api_packages) --output-package $(kube_clientset_package) -h 
$(kube_go_header_text) --apply-configuration-package 
$(kube_applyconfiguration_package)
+       # generate listers for kube api types
+       @$(lister_gen) --input-dirs $(kube_api_packages) --output-package 
$(kube_listers_package) -h $(kube_go_header_text)
+       # generate informers for kube api types
+       @$(informer_gen) --input-dirs $(kube_api_packages) 
--versioned-clientset-package $(kube_clientset_package)/$(kube_clientset_name) 
--listers-package $(kube_listers_package) --output-package 
$(kube_informers_package) -h $(kube_go_header_text)
+       @$(move_generated)
+       @$(rename_generated_files)
+       @$(fixup_generated_files)
+
+.PHONY: clean-k8s-client
+clean-k8s-client:
+    # remove generated code
+       @rm -rf client-go/pkg
+
+include Makefile.common.mk
diff --git a/Makefile.overrides.mk b/Makefile.overrides.mk
new file mode 100644
index 00000000..de678eac
--- /dev/null
+++ b/Makefile.overrides.mk
@@ -0,0 +1 @@
+BUILD_WITH_CONTAINER ?= 1
diff --git a/api/networking/v1alpha3/destination_rule.pb.go 
b/api/networking/v1alpha3/destination_rule.pb.go
index 587bbd24..bb1802ca 100644
--- a/api/networking/v1alpha3/destination_rule.pb.go
+++ b/api/networking/v1alpha3/destination_rule.pb.go
@@ -93,6 +93,22 @@ func (ClientTLSSettings_TLSmode) EnumDescriptor() ([]byte, 
[]int) {
        return file_networking_v1alpha3_destination_rule_proto_rawDescGZIP(), 
[]int{3, 0}
 }
 
+// <!-- crd generation tags
+// +cue-gen:DestinationRule:groupName:networking.dubbo.apache.org
+// +cue-gen:DestinationRule:versions:v1alpha3
+// +cue-gen:DestinationRule:annotations:helm.sh/resource-policy=keep
+// 
+cue-gen:DestinationRule:labels:app=dubbo-planet,chart=dubbo,heritage=Tiller,release=dubbo
+// +cue-gen:DestinationRule:subresource:status
+// +cue-gen:DestinationRule:scope:Namespaced
+// +cue-gen:DestinationRule:resource:categories=dubbo,networking,shortNames=dr
+// 
+cue-gen:DestinationRule:printerColumn:name=Host,type=string,JSONPath=.spec.host,description="The
 name of a service from the service registry"
+// 
+cue-gen:DestinationRule:printerColumn:name=Age,type=date,JSONPath=.metadata.creationTimestamp,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. For more information, 
see [Kubernetes API 
Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata)"
+// +cue-gen:DestinationRule:preserveUnknownFields:false
+// -->
+//
 // <!-- go code generation tags
 // +kubetype-gen
 // +kubetype-gen:groupVersion=networking.dubbo.apache.org/v1alpha3
diff --git a/api/networking/v1alpha3/destination_rule.proto 
b/api/networking/v1alpha3/destination_rule.proto
index 154044c3..0e90d87b 100644
--- a/api/networking/v1alpha3/destination_rule.proto
+++ b/api/networking/v1alpha3/destination_rule.proto
@@ -28,6 +28,21 @@ import "networking/v1alpha3/virtual_service.proto";
 
 option go_package = "/api/networking/v1alpha3";
 
+// <!-- crd generation tags
+// +cue-gen:DestinationRule:groupName:networking.dubbo.apache.org
+// +cue-gen:DestinationRule:versions:v1alpha3
+// +cue-gen:DestinationRule:annotations:helm.sh/resource-policy=keep
+// 
+cue-gen:DestinationRule:labels:app=dubbo-planet,chart=dubbo,heritage=Tiller,release=dubbo
+// +cue-gen:DestinationRule:subresource:status
+// +cue-gen:DestinationRule:scope:Namespaced
+// +cue-gen:DestinationRule:resource:categories=dubbo,networking,shortNames=dr
+// 
+cue-gen:DestinationRule:printerColumn:name=Host,type=string,JSONPath=.spec.host,description="The
 name of a service from the service registry"
+// 
+cue-gen:DestinationRule:printerColumn:name=Age,type=date,JSONPath=.metadata.creationTimestamp,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. For more information, 
see [Kubernetes API 
Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata)"
+// +cue-gen:DestinationRule:preserveUnknownFields:false
+// -->
 //
 // <!-- go code generation tags
 // +kubetype-gen
diff --git a/api/networking/v1alpha3/virtual_service.pb.go 
b/api/networking/v1alpha3/virtual_service.pb.go
index 5f92d8a1..762719c9 100644
--- a/api/networking/v1alpha3/virtual_service.pb.go
+++ b/api/networking/v1alpha3/virtual_service.pb.go
@@ -40,6 +40,22 @@ const (
        _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 )
 
+// <!-- crd generation tags
+// +cue-gen:VirtualService:groupName:networking.dubbo.apache.org
+// +cue-gen:VirtualService:versions:v1alpha3
+// +cue-gen:VirtualService:annotations:helm.sh/resource-policy=keep
+// 
+cue-gen:VirtualService:labels:app=dubbo-planet,chart=dubbo,heritage=Tiller,release=dubbo
+// +cue-gen:VirtualService:subresource:status
+// +cue-gen:VirtualService:scope:Namespaced
+// +cue-gen:VirtualService:resource:categories=dubbo,networking,shortNames=vs
+// 
+cue-gen:VirtualService:printerColumn:name=Hosts,type=string,JSONPath=.spec.hosts,description="The
 destination hosts to which traffic is being sent"
+// 
+cue-gen:VirtualService:printerColumn:name=Age,type=date,JSONPath=.metadata.creationTimestamp,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";
+// +cue-gen:VirtualService:preserveUnknownFields:false
+// -->
+//
 // <!-- go code generation tags
 // +kubetype-gen
 // +kubetype-gen:groupVersion=networking.dubbo.apache.org/v1alpha3
diff --git a/api/networking/v1alpha3/virtual_service.proto 
b/api/networking/v1alpha3/virtual_service.proto
index a9c01ecd..67a00101 100644
--- a/api/networking/v1alpha3/virtual_service.proto
+++ b/api/networking/v1alpha3/virtual_service.proto
@@ -24,6 +24,21 @@ import "google/protobuf/wrappers.proto";
 
 option go_package = "/api/networking/v1alpha3";
 
+// <!-- crd generation tags
+// +cue-gen:VirtualService:groupName:networking.dubbo.apache.org
+// +cue-gen:VirtualService:versions:v1alpha3
+// +cue-gen:VirtualService:annotations:helm.sh/resource-policy=keep
+// 
+cue-gen:VirtualService:labels:app=dubbo-planet,chart=dubbo,heritage=Tiller,release=dubbo
+// +cue-gen:VirtualService:subresource:status
+// +cue-gen:VirtualService:scope:Namespaced
+// +cue-gen:VirtualService:resource:categories=dubbo,networking,shortNames=vs
+// 
+cue-gen:VirtualService:printerColumn:name=Hosts,type=string,JSONPath=.spec.hosts,description="The
 destination hosts to which traffic is being sent"
+// 
+cue-gen:VirtualService:printerColumn:name=Age,type=date,JSONPath=.metadata.creationTimestamp,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";
+// +cue-gen:VirtualService:preserveUnknownFields:false
+// -->
 //
 // <!-- go code generation tags
 // +kubetype-gen
diff --git a/client-go/pkg/apis/networking/v1alpha3/doc.go 
b/client-go/pkg/apis/networking/v1alpha3/doc.go
new file mode 100644
index 00000000..27561001
--- /dev/null
+++ b/client-go/pkg/apis/networking/v1alpha3/doc.go
@@ -0,0 +1,23 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 kubetype-gen. DO NOT EDIT.
+
+// Package has auto-generated kube type wrappers for raw types.
+// +k8s:openapi-gen=true
+// +k8s:deepcopy-gen=package
+// +groupName=networking.dubbo.apache.org
+package v1alpha3
diff --git a/client-go/pkg/apis/networking/v1alpha3/register.go 
b/client-go/pkg/apis/networking/v1alpha3/register.go
new file mode 100644
index 00000000..146157b2
--- /dev/null
+++ b/client-go/pkg/apis/networking/v1alpha3/register.go
@@ -0,0 +1,53 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 kubetype-gen. DO NOT EDIT.
+
+package v1alpha3
+
+import (
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       runtime "k8s.io/apimachinery/pkg/runtime"
+       schema "k8s.io/apimachinery/pkg/runtime/schema"
+)
+
+var (
+       // Package-wide variables from generator "register".
+       SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: 
"v1alpha3"}
+       SchemeBuilder      = runtime.NewSchemeBuilder(addKnownTypes)
+       localSchemeBuilder = &SchemeBuilder
+       AddToScheme        = localSchemeBuilder.AddToScheme
+)
+
+const (
+       // Package-wide consts from generator "register".
+       GroupName = "networking.dubbo.apache.org"
+)
+
+func Resource(resource string) schema.GroupResource {
+       return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
+
+func addKnownTypes(scheme *runtime.Scheme) error {
+       scheme.AddKnownTypes(SchemeGroupVersion,
+               &DestinationRule{},
+               &DestinationRuleList{},
+               &VirtualService{},
+               &VirtualServiceList{},
+       )
+       v1.AddToGroupVersion(scheme, SchemeGroupVersion)
+       return nil
+}
diff --git a/client-go/pkg/apis/networking/v1alpha3/types.go 
b/client-go/pkg/apis/networking/v1alpha3/types.go
new file mode 100644
index 00000000..bc5cb2df
--- /dev/null
+++ b/client-go/pkg/apis/networking/v1alpha3/types.go
@@ -0,0 +1,119 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 kubetype-gen. DO NOT EDIT.
+
+package v1alpha3
+
+import (
+       networkingv1alpha3 "./api/networking/v1alpha3"
+       v1alpha1 "github.com/apache/dubbo-kubernetes/api/meta/v1alpha1"
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+//
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// <!-- crd generation tags
+// +cue-gen:DestinationRule:groupName:networking.dubbo.apache.org
+// +cue-gen:DestinationRule:versions:v1alpha3
+// +cue-gen:DestinationRule:annotations:helm.sh/resource-policy=keep
+// 
+cue-gen:DestinationRule:labels:app=dubbo-planet,chart=dubbo,heritage=Tiller,release=dubbo
+// +cue-gen:DestinationRule:subresource:status
+// +cue-gen:DestinationRule:scope:Namespaced
+// +cue-gen:DestinationRule:resource:categories=dubbo,networking,shortNames=dr
+// 
+cue-gen:DestinationRule:printerColumn:name=Host,type=string,JSONPath=.spec.host,description="The
 name of a service from the service registry"
+// 
+cue-gen:DestinationRule:printerColumn:name=Age,type=date,JSONPath=.metadata.creationTimestamp,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. For more information, 
see [Kubernetes API 
Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata)"
+// +cue-gen:DestinationRule:preserveUnknownFields:false
+// -->
+//
+// <!-- go code generation tags
+// +kubetype-gen
+// +kubetype-gen:groupVersion=networking.dubbo.apache.org/v1alpha3
+// +genclient
+// +k8s:deepcopy-gen=true
+// -->
+type DestinationRule struct {
+       v1.TypeMeta `json:",inline"`
+       // +optional
+       v1.ObjectMeta `json:"metadata,omitempty" 
protobuf:"bytes,1,opt,name=metadata"`
+
+       // Spec defines the implementation of this definition.
+       // +optional
+       Spec networkingv1alpha3.DestinationRule `json:"spec,omitempty" 
protobuf:"bytes,2,opt,name=spec"`
+
+       Status v1alpha1.DubboStatus `json:"status,omitempty"`
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// DestinationRuleList is a collection of DestinationRules.
+type DestinationRuleList struct {
+       v1.TypeMeta `json:",inline"`
+       // +optional
+       v1.ListMeta `json:"metadata,omitempty" 
protobuf:"bytes,1,opt,name=metadata"`
+       Items       []*DestinationRule `json:"items" 
protobuf:"bytes,2,rep,name=items"`
+}
+
+//
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// <!-- crd generation tags
+// +cue-gen:VirtualService:groupName:networking.dubbo.apache.org
+// +cue-gen:VirtualService:versions:v1alpha3
+// +cue-gen:VirtualService:annotations:helm.sh/resource-policy=keep
+// 
+cue-gen:VirtualService:labels:app=dubbo-planet,chart=dubbo,heritage=Tiller,release=dubbo
+// +cue-gen:VirtualService:subresource:status
+// +cue-gen:VirtualService:scope:Namespaced
+// +cue-gen:VirtualService:resource:categories=dubbo,networking,shortNames=vs
+// 
+cue-gen:VirtualService:printerColumn:name=Hosts,type=string,JSONPath=.spec.hosts,description="The
 destination hosts to which traffic is being sent"
+// 
+cue-gen:VirtualService:printerColumn:name=Age,type=date,JSONPath=.metadata.creationTimestamp,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";
+// +cue-gen:VirtualService:preserveUnknownFields:false
+// -->
+//
+// <!-- go code generation tags
+// +kubetype-gen
+// +kubetype-gen:groupVersion=networking.dubbo.apache.org/v1alpha3
+// +genclient
+// +k8s:deepcopy-gen=true
+// -->
+type VirtualService struct {
+       v1.TypeMeta `json:",inline"`
+       // +optional
+       v1.ObjectMeta `json:"metadata,omitempty" 
protobuf:"bytes,1,opt,name=metadata"`
+
+       // Spec defines the implementation of this definition.
+       // +optional
+       Spec networkingv1alpha3.VirtualService `json:"spec,omitempty" 
protobuf:"bytes,2,opt,name=spec"`
+
+       Status v1alpha1.DubboStatus `json:"status,omitempty"`
+}
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// VirtualServiceList is a collection of VirtualServices.
+type VirtualServiceList struct {
+       v1.TypeMeta `json:",inline"`
+       // +optional
+       v1.ListMeta `json:"metadata,omitempty" 
protobuf:"bytes,1,opt,name=metadata"`
+       Items       []*VirtualService `json:"items" 
protobuf:"bytes,2,rep,name=items"`
+}
diff --git a/client-go/pkg/applyconfiguration/internal/internal.go 
b/client-go/pkg/applyconfiguration/internal/internal.go
new file mode 100644
index 00000000..97948bcd
--- /dev/null
+++ b/client-go/pkg/applyconfiguration/internal/internal.go
@@ -0,0 +1,62 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 applyconfiguration-gen. DO NOT EDIT.
+
+package internal
+
+import (
+       "fmt"
+       "sync"
+
+       typed "sigs.k8s.io/structured-merge-diff/v4/typed"
+)
+
+func Parser() *typed.Parser {
+       parserOnce.Do(func() {
+               var err error
+               parser, err = typed.NewParser(schemaYAML)
+               if err != nil {
+                       panic(fmt.Sprintf("Failed to parse schema: %v", err))
+               }
+       })
+       return parser
+}
+
+var parserOnce sync.Once
+var parser *typed.Parser
+var schemaYAML = typed.YAMLObject(`types:
+- name: __untyped_atomic_
+  scalar: untyped
+  list:
+    elementType:
+      namedType: __untyped_atomic_
+    elementRelationship: atomic
+  map:
+    elementType:
+      namedType: __untyped_atomic_
+    elementRelationship: atomic
+- name: __untyped_deduced_
+  scalar: untyped
+  list:
+    elementType:
+      namedType: __untyped_atomic_
+    elementRelationship: atomic
+  map:
+    elementType:
+      namedType: __untyped_deduced_
+    elementRelationship: separable
+`)
diff --git a/client-go/pkg/applyconfiguration/meta/v1/objectmeta.go 
b/client-go/pkg/applyconfiguration/meta/v1/objectmeta.go
new file mode 100644
index 00000000..a8e58a80
--- /dev/null
+++ b/client-go/pkg/applyconfiguration/meta/v1/objectmeta.go
@@ -0,0 +1,172 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1
+
+import (
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       types "k8s.io/apimachinery/pkg/types"
+       metav1 "k8s.io/client-go/applyconfigurations/meta/v1"
+)
+
+// ObjectMetaApplyConfiguration represents an declarative configuration of the 
ObjectMeta type for use
+// with apply.
+type ObjectMetaApplyConfiguration struct {
+       Name                       *string                                   
`json:"name,omitempty"`
+       GenerateName               *string                                   
`json:"generateName,omitempty"`
+       Namespace                  *string                                   
`json:"namespace,omitempty"`
+       UID                        *types.UID                                
`json:"uid,omitempty"`
+       ResourceVersion            *string                                   
`json:"resourceVersion,omitempty"`
+       Generation                 *int64                                    
`json:"generation,omitempty"`
+       CreationTimestamp          *v1.Time                                  
`json:"creationTimestamp,omitempty"`
+       DeletionTimestamp          *v1.Time                                  
`json:"deletionTimestamp,omitempty"`
+       DeletionGracePeriodSeconds *int64                                    
`json:"deletionGracePeriodSeconds,omitempty"`
+       Labels                     map[string]string                         
`json:"labels,omitempty"`
+       Annotations                map[string]string                         
`json:"annotations,omitempty"`
+       OwnerReferences            []metav1.OwnerReferenceApplyConfiguration 
`json:"ownerReferences,omitempty"`
+       Finalizers                 []string                                  
`json:"finalizers,omitempty"`
+}
+
+// ObjectMetaApplyConfiguration constructs an declarative configuration of the 
ObjectMeta type for use with
+// apply.
+func ObjectMeta() *ObjectMetaApplyConfiguration {
+       return &ObjectMetaApplyConfiguration{}
+}
+
+// WithName sets the Name field in the declarative configuration to the given 
value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the Name field is set to the value of the last 
call.
+func (b *ObjectMetaApplyConfiguration) WithName(value string) 
*ObjectMetaApplyConfiguration {
+       b.Name = &value
+       return b
+}
+
+// WithGenerateName sets the GenerateName field in the declarative 
configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the GenerateName field is set to the value of the 
last call.
+func (b *ObjectMetaApplyConfiguration) WithGenerateName(value string) 
*ObjectMetaApplyConfiguration {
+       b.GenerateName = &value
+       return b
+}
+
+// WithNamespace sets the Namespace field in the declarative configuration to 
the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the Namespace field is set to the value of the 
last call.
+func (b *ObjectMetaApplyConfiguration) WithNamespace(value string) 
*ObjectMetaApplyConfiguration {
+       b.Namespace = &value
+       return b
+}
+
+// WithUID sets the UID field in the declarative configuration to the given 
value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the UID field is set to the value of the last 
call.
+func (b *ObjectMetaApplyConfiguration) WithUID(value types.UID) 
*ObjectMetaApplyConfiguration {
+       b.UID = &value
+       return b
+}
+
+// WithResourceVersion sets the ResourceVersion field in the declarative 
configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the ResourceVersion field is set to the value of 
the last call.
+func (b *ObjectMetaApplyConfiguration) WithResourceVersion(value string) 
*ObjectMetaApplyConfiguration {
+       b.ResourceVersion = &value
+       return b
+}
+
+// WithGeneration sets the Generation field in the declarative configuration 
to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the Generation field is set to the value of the 
last call.
+func (b *ObjectMetaApplyConfiguration) WithGeneration(value int64) 
*ObjectMetaApplyConfiguration {
+       b.Generation = &value
+       return b
+}
+
+// WithCreationTimestamp sets the CreationTimestamp field in the declarative 
configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the CreationTimestamp field is set to the value 
of the last call.
+func (b *ObjectMetaApplyConfiguration) WithCreationTimestamp(value v1.Time) 
*ObjectMetaApplyConfiguration {
+       b.CreationTimestamp = &value
+       return b
+}
+
+// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative 
configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the DeletionTimestamp field is set to the value 
of the last call.
+func (b *ObjectMetaApplyConfiguration) WithDeletionTimestamp(value v1.Time) 
*ObjectMetaApplyConfiguration {
+       b.DeletionTimestamp = &value
+       return b
+}
+
+// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in 
the declarative configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the DeletionGracePeriodSeconds field is set to 
the value of the last call.
+func (b *ObjectMetaApplyConfiguration) WithDeletionGracePeriodSeconds(value 
int64) *ObjectMetaApplyConfiguration {
+       b.DeletionGracePeriodSeconds = &value
+       return b
+}
+
+// WithLabels puts the entries into the Labels field in the declarative 
configuration
+// and returns the receiver, so that objects can be build by chaining "With" 
function invocations.
+// If called multiple times, the entries provided by each call will be put on 
the Labels field,
+// overwriting an existing map entries in Labels field with the same key.
+func (b *ObjectMetaApplyConfiguration) WithLabels(entries map[string]string) 
*ObjectMetaApplyConfiguration {
+       if b.Labels == nil && len(entries) > 0 {
+               b.Labels = make(map[string]string, len(entries))
+       }
+       for k, v := range entries {
+               b.Labels[k] = v
+       }
+       return b
+}
+
+// WithAnnotations puts the entries into the Annotations field in the 
declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" 
function invocations.
+// If called multiple times, the entries provided by each call will be put on 
the Annotations field,
+// overwriting an existing map entries in Annotations field with the same key.
+func (b *ObjectMetaApplyConfiguration) WithAnnotations(entries 
map[string]string) *ObjectMetaApplyConfiguration {
+       if b.Annotations == nil && len(entries) > 0 {
+               b.Annotations = make(map[string]string, len(entries))
+       }
+       for k, v := range entries {
+               b.Annotations[k] = v
+       }
+       return b
+}
+
+// WithOwnerReferences adds the given value to the OwnerReferences field in 
the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" 
function invocations.
+// If called multiple times, values provided by each call will be appended to 
the OwnerReferences field.
+func (b *ObjectMetaApplyConfiguration) WithOwnerReferences(values 
...*metav1.OwnerReferenceApplyConfiguration) *ObjectMetaApplyConfiguration {
+       for i := range values {
+               if values[i] == nil {
+                       panic("nil value passed to WithOwnerReferences")
+               }
+               b.OwnerReferences = append(b.OwnerReferences, *values[i])
+       }
+       return b
+}
+
+// WithFinalizers adds the given value to the Finalizers field in the 
declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" 
function invocations.
+// If called multiple times, values provided by each call will be appended to 
the Finalizers field.
+func (b *ObjectMetaApplyConfiguration) WithFinalizers(values ...string) 
*ObjectMetaApplyConfiguration {
+       for i := range values {
+               b.Finalizers = append(b.Finalizers, values[i])
+       }
+       return b
+}
diff --git a/client-go/pkg/applyconfiguration/meta/v1/ownerreference.go 
b/client-go/pkg/applyconfiguration/meta/v1/ownerreference.go
new file mode 100644
index 00000000..45ea2c86
--- /dev/null
+++ b/client-go/pkg/applyconfiguration/meta/v1/ownerreference.go
@@ -0,0 +1,88 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1
+
+import (
+       types "k8s.io/apimachinery/pkg/types"
+)
+
+// OwnerReferenceApplyConfiguration represents an declarative configuration of 
the OwnerReference type for use
+// with apply.
+type OwnerReferenceApplyConfiguration struct {
+       APIVersion         *string    `json:"apiVersion,omitempty"`
+       Kind               *string    `json:"kind,omitempty"`
+       Name               *string    `json:"name,omitempty"`
+       UID                *types.UID `json:"uid,omitempty"`
+       Controller         *bool      `json:"controller,omitempty"`
+       BlockOwnerDeletion *bool      `json:"blockOwnerDeletion,omitempty"`
+}
+
+// OwnerReferenceApplyConfiguration constructs an declarative configuration of 
the OwnerReference type for use with
+// apply.
+func OwnerReference() *OwnerReferenceApplyConfiguration {
+       return &OwnerReferenceApplyConfiguration{}
+}
+
+// WithAPIVersion sets the APIVersion field in the declarative configuration 
to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the APIVersion field is set to the value of the 
last call.
+func (b *OwnerReferenceApplyConfiguration) WithAPIVersion(value string) 
*OwnerReferenceApplyConfiguration {
+       b.APIVersion = &value
+       return b
+}
+
+// WithKind sets the Kind field in the declarative configuration to the given 
value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the Kind field is set to the value of the last 
call.
+func (b *OwnerReferenceApplyConfiguration) WithKind(value string) 
*OwnerReferenceApplyConfiguration {
+       b.Kind = &value
+       return b
+}
+
+// WithName sets the Name field in the declarative configuration to the given 
value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the Name field is set to the value of the last 
call.
+func (b *OwnerReferenceApplyConfiguration) WithName(value string) 
*OwnerReferenceApplyConfiguration {
+       b.Name = &value
+       return b
+}
+
+// WithUID sets the UID field in the declarative configuration to the given 
value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the UID field is set to the value of the last 
call.
+func (b *OwnerReferenceApplyConfiguration) WithUID(value types.UID) 
*OwnerReferenceApplyConfiguration {
+       b.UID = &value
+       return b
+}
+
+// WithController sets the Controller field in the declarative configuration 
to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the Controller field is set to the value of the 
last call.
+func (b *OwnerReferenceApplyConfiguration) WithController(value bool) 
*OwnerReferenceApplyConfiguration {
+       b.Controller = &value
+       return b
+}
+
+// WithBlockOwnerDeletion sets the BlockOwnerDeletion field in the declarative 
configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the BlockOwnerDeletion field is set to the value 
of the last call.
+func (b *OwnerReferenceApplyConfiguration) WithBlockOwnerDeletion(value bool) 
*OwnerReferenceApplyConfiguration {
+       b.BlockOwnerDeletion = &value
+       return b
+}
diff --git a/client-go/pkg/applyconfiguration/meta/v1/typemeta.go 
b/client-go/pkg/applyconfiguration/meta/v1/typemeta.go
new file mode 100644
index 00000000..b7e00bf7
--- /dev/null
+++ b/client-go/pkg/applyconfiguration/meta/v1/typemeta.go
@@ -0,0 +1,48 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 applyconfiguration-gen. DO NOT EDIT.
+
+package v1
+
+// TypeMetaApplyConfiguration represents an declarative configuration of the 
TypeMeta type for use
+// with apply.
+type TypeMetaApplyConfiguration struct {
+       Kind       *string `json:"kind,omitempty"`
+       APIVersion *string `json:"apiVersion,omitempty"`
+}
+
+// TypeMetaApplyConfiguration constructs an declarative configuration of the 
TypeMeta type for use with
+// apply.
+func TypeMeta() *TypeMetaApplyConfiguration {
+       return &TypeMetaApplyConfiguration{}
+}
+
+// WithKind sets the Kind field in the declarative configuration to the given 
value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the Kind field is set to the value of the last 
call.
+func (b *TypeMetaApplyConfiguration) WithKind(value string) 
*TypeMetaApplyConfiguration {
+       b.Kind = &value
+       return b
+}
+
+// WithAPIVersion sets the APIVersion field in the declarative configuration 
to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the APIVersion field is set to the value of the 
last call.
+func (b *TypeMetaApplyConfiguration) WithAPIVersion(value string) 
*TypeMetaApplyConfiguration {
+       b.APIVersion = &value
+       return b
+}
diff --git a/client-go/pkg/applyconfiguration/utils.go 
b/client-go/pkg/applyconfiguration/utils.go
new file mode 100644
index 00000000..8a3650f7
--- /dev/null
+++ b/client-go/pkg/applyconfiguration/utils.go
@@ -0,0 +1,41 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 applyconfiguration-gen. DO NOT EDIT.
+
+package applyconfiguration
+
+import (
+       v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       schema "k8s.io/apimachinery/pkg/runtime/schema"
+       metav1 "k8s.io/client-go/applyconfigurations/meta/v1"
+)
+
+// ForKind returns an apply configuration type for the given GroupVersionKind, 
or nil if no
+// apply configuration type exists for the given GroupVersionKind.
+func ForKind(kind schema.GroupVersionKind) interface{} {
+       switch kind {
+       // Group=meta.k8s.io, Version=v1
+       case v1.SchemeGroupVersion.WithKind("ObjectMeta"):
+               return &metav1.ObjectMetaApplyConfiguration{}
+       case v1.SchemeGroupVersion.WithKind("OwnerReference"):
+               return &metav1.OwnerReferenceApplyConfiguration{}
+       case v1.SchemeGroupVersion.WithKind("TypeMeta"):
+               return &metav1.TypeMetaApplyConfiguration{}
+
+       }
+       return nil
+}
diff --git a/client-go/pkg/clientset/versioned/clientset.go 
b/client-go/pkg/clientset/versioned/clientset.go
new file mode 100644
index 00000000..4c2c0bca
--- /dev/null
+++ b/client-go/pkg/clientset/versioned/clientset.go
@@ -0,0 +1,19 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 versioned
diff --git a/client-go/pkg/clientset/versioned/fake/clientset_generated.go 
b/client-go/pkg/clientset/versioned/fake/clientset_generated.go
new file mode 100644
index 00000000..4f3f17a1
--- /dev/null
+++ b/client-go/pkg/clientset/versioned/fake/clientset_generated.go
@@ -0,0 +1,19 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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
diff --git a/client-go/pkg/clientset/versioned/fake/doc.go 
b/client-go/pkg/clientset/versioned/fake/doc.go
new file mode 100644
index 00000000..6f3e45fb
--- /dev/null
+++ b/client-go/pkg/clientset/versioned/fake/doc.go
@@ -0,0 +1,20 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// This package has the automatically generated fake clientset.
+package fake
diff --git a/client-go/pkg/clientset/versioned/fake/register.go 
b/client-go/pkg/clientset/versioned/fake/register.go
new file mode 100644
index 00000000..4f3f17a1
--- /dev/null
+++ b/client-go/pkg/clientset/versioned/fake/register.go
@@ -0,0 +1,19 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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
diff --git a/client-go/pkg/clientset/versioned/scheme/doc.go 
b/client-go/pkg/clientset/versioned/scheme/doc.go
new file mode 100644
index 00000000..8931dc53
--- /dev/null
+++ b/client-go/pkg/clientset/versioned/scheme/doc.go
@@ -0,0 +1,20 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// This package contains the scheme of the automatically generated clientset.
+package scheme
diff --git a/client-go/pkg/clientset/versioned/scheme/register.go 
b/client-go/pkg/clientset/versioned/scheme/register.go
new file mode 100644
index 00000000..a9569ec0
--- /dev/null
+++ b/client-go/pkg/clientset/versioned/scheme/register.go
@@ -0,0 +1,19 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 scheme
diff --git a/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/doc.go 
b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/doc.go
new file mode 100644
index 00000000..1697aa54
--- /dev/null
+++ b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/doc.go
@@ -0,0 +1,20 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// This package has the automatically generated typed clients.
+package v1alpha3
diff --git 
a/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/fake/doc.go 
b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/fake/doc.go
new file mode 100644
index 00000000..cccb459f
--- /dev/null
+++ b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/fake/doc.go
@@ -0,0 +1,20 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 has the automatically generated clients.
+package fake
diff --git 
a/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/fake/fake_networking_client.go
 
b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/fake/fake_networking_client.go
new file mode 100644
index 00000000..4f3f17a1
--- /dev/null
+++ 
b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/fake/fake_networking_client.go
@@ -0,0 +1,19 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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
diff --git 
a/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/generated_expansion.go
 
b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/generated_expansion.go
new file mode 100644
index 00000000..1ce7eb7a
--- /dev/null
+++ 
b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/generated_expansion.go
@@ -0,0 +1,19 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 v1alpha3
diff --git 
a/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/networking_client.go
 
b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/networking_client.go
new file mode 100644
index 00000000..1ce7eb7a
--- /dev/null
+++ 
b/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/networking_client.go
@@ -0,0 +1,19 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 v1alpha3
diff --git a/go.mod b/go.mod
index 6efb735a..53ae3e92 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,12 @@ module github.com/apache/dubbo-kubernetes
 
 go 1.24.0
 
+require (
+       k8s.io/apimachinery v0.34.1
+       k8s.io/client-go v0.34.1
+       sigs.k8s.io/structured-merge-diff/v4 v4.6.0
+)
+
 require (
        github.com/AlecAivazis/survey/v2 v2.3.7
        github.com/Masterminds/sprig/v3 v3.3.0
@@ -79,8 +85,6 @@ require (
        istio.io/client-go v1.27.1
        k8s.io/api v0.34.1
        k8s.io/apiextensions-apiserver v0.34.1
-       k8s.io/apimachinery v0.34.1
-       k8s.io/client-go v0.34.1
        k8s.io/gengo v0.0.0-20251215205346-5ee0d033ba5b
        k8s.io/klog/v2 v2.130.1
        k8s.io/kubectl v0.33.3
@@ -273,5 +277,4 @@ replace (
        github.com/google/go-containerregistry => 
github.com/google/go-containerregistry v0.20.2
        github.com/moby/buildkit => github.com/moby/buildkit v0.10.6
        github.com/moby/dockerfile => github.com/moby/dockerfile v1.4.1
-
 )
diff --git a/go.sum b/go.sum
index 581e42ff..53b06856 100644
--- a/go.sum
+++ b/go.sum
@@ -361,6 +361,7 @@ github.com/google/go-cmp v0.4.0/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.1/go.mod 
h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.8/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod 
h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/go-containerregistry v0.20.2 
h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
@@ -932,10 +933,14 @@ sigs.k8s.io/kustomize/kyaml v0.19.0 
h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XS
 sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod 
h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
 sigs.k8s.io/mcs-api v0.2.0 h1:F8o/nIpQmog494Qwe94srDWjS3ltEu4y5IL9i3dB938=
 sigs.k8s.io/mcs-api v0.2.0/go.mod 
h1:zZ5CK8uS6HaLkxY4HqsmcBHfzHuNMrY2uJy8T7jffK4=
+sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod 
h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
 sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
 sigs.k8s.io/randfill v1.0.0/go.mod 
h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
+sigs.k8s.io/structured-merge-diff/v4 v4.6.0 
h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
+sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod 
h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
 sigs.k8s.io/structured-merge-diff/v6 v6.3.0 
h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
 sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod 
h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
 sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
 sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/header.go.txt b/header.go.txt
new file mode 100644
index 00000000..bf64d9fa
--- /dev/null
+++ b/header.go.txt
@@ -0,0 +1,16 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
diff --git a/kubetype-gen b/kubetype-gen
deleted file mode 100755
index 3ec817ea..00000000
Binary files a/kubetype-gen and /dev/null differ
diff --git a/tests/grpc-app/.dockerignore b/tests/grpc-app/.dockerignore
deleted file mode 100644
index e29c0862..00000000
--- a/tests/grpc-app/.dockerignore
+++ /dev/null
@@ -1,21 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
-*.md
-bin/
-*.pb.go
-.git/
-.gitignore
-README.md
\ No newline at end of file
diff --git a/tests/grpc-app/.gitignore b/tests/grpc-app/.gitignore
deleted file mode 100644
index db441f49..00000000
--- a/tests/grpc-app/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-bin/
-*.pb.go
diff --git a/tests/grpc-app/README.md b/tests/grpc-app/README.md
deleted file mode 100644
index a88ecf5f..00000000
--- a/tests/grpc-app/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# gRPC Application
-
-This is a test example for gRPC proxyless service mesh based on [Istio's blog 
post](https://istio.io/latest/blog/2021/proxyless-grpc/).
-
-## Architecture
-
-- **Provider**: gRPC server with xDS support (port 17070). This service is 
deployed with multiple versions (v1/v2) to demonstrate gray 
release/traffic-splitting scenarios and exposes gRPC reflection so `grpcurl` 
can query it directly.
-- **Consumer**: gRPC client with xDS support + test server (port 17171). This 
component drives load toward the provider service for automated tests.
-
-Both services use `dubbo-proxy` sidecar as an xDS proxy to connect to the 
control plane. The sidecar runs an xDS proxy server that listens on a Unix 
Domain Socket (UDS) at `/etc/dubbo/proxy/XDS`. The gRPC applications connect to 
this xDS proxy via the UDS socket using the `GRPC_XDS_BOOTSTRAP` environment 
variable.
-
-**Note**: This is "proxyless" in the sense that the applications use native 
gRPC xDS clients instead of Envoy proxy for traffic routing. However, a 
lightweight sidecar (`dubbo-proxy`) is still used to proxy xDS API calls 
between the gRPC clients and the control plane.
diff --git a/tests/grpc-app/consumer/main.go b/tests/grpc-app/consumer/main.go
deleted file mode 100644
index c5dad251..00000000
--- a/tests/grpc-app/consumer/main.go
+++ /dev/null
@@ -1,739 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You 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 main
-
-import (
-       "context"
-       "encoding/json"
-       "flag"
-       "fmt"
-       "log"
-       "net"
-       "os"
-       "os/signal"
-       "regexp"
-       "strings"
-       "sync"
-       "syscall"
-       "time"
-
-       "google.golang.org/grpc"
-       "google.golang.org/grpc/connectivity"
-       "google.golang.org/grpc/credentials/insecure"
-       xdscreds "google.golang.org/grpc/credentials/xds"
-       "google.golang.org/grpc/grpclog"
-       "google.golang.org/grpc/reflection"
-       "google.golang.org/grpc/status"
-       _ "google.golang.org/grpc/xds"
-
-       pb "github.com/apache/dubbo-kubernetes/tests/grpc-app/proto"
-)
-
-var (
-       port       = flag.Int("port", 17171, "gRPC server port for ForwardEcho 
testing")
-       testServer *grpc.Server
-)
-
-// grpcLogger filters out xDS informational logs that are incorrectly marked 
as ERROR
-type grpcLogger struct {
-       logger *log.Logger
-}
-
-var (
-       // Regex to match gRPC formatting errors like %!p(...)
-       formatErrorRegex = regexp.MustCompile(`%!p\([^)]+\)`)
-)
-
-// cleanMessage removes formatting errors from gRPC logs
-// Fixes issues like: 
"\u003c%!p(networktype.keyType=grpc.internal.transport.networktype)\u003e": 
"unix"
-func cleanMessage(msg string) string {
-       // Replace %!p(...) patterns with a cleaner representation
-       msg = formatErrorRegex.ReplaceAllStringFunc(msg, func(match string) 
string {
-               // Extract the key from %!p(networktype.keyType=...)
-               if strings.Contains(match, "networktype.keyType") {
-                       return `"networktype"`
-               }
-               // For other cases, just remove the error pattern
-               return ""
-       })
-       // Also clean up Unicode escape sequences that appear with formatting 
errors
-       // Replace \u003c (which is <) and \u003e (which is >) when they appear 
with formatting errors
-       msg = strings.ReplaceAll(msg, `\u003c`, "<")
-       msg = strings.ReplaceAll(msg, `\u003e`, ">")
-       // Clean up patterns like <...>: "unix" to just show the value
-       msg = regexp.MustCompile(`<[^>]*>:\s*"unix"`).ReplaceAllString(msg, 
`"networktype": "unix"`)
-       return msg
-}
-
-func (l *grpcLogger) Info(args ...interface{}) {
-       msg := fmt.Sprint(args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Print("INFO: ", msg)
-}
-
-func (l *grpcLogger) Infoln(args ...interface{}) {
-       msg := fmt.Sprintln(args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Print("INFO: ", msg)
-}
-
-func (l *grpcLogger) Infof(format string, args ...interface{}) {
-       msg := fmt.Sprintf(format, args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Printf("INFO: %s", msg)
-}
-
-func (l *grpcLogger) Warning(args ...interface{}) {
-       msg := cleanMessage(fmt.Sprint(args...))
-       l.logger.Print("WARNING: ", msg)
-}
-
-func (l *grpcLogger) Warningln(args ...interface{}) {
-       msg := cleanMessage(fmt.Sprintln(args...))
-       l.logger.Print("WARNING: ", msg)
-}
-
-func (l *grpcLogger) Warningf(format string, args ...interface{}) {
-       msg := cleanMessage(fmt.Sprintf(format, args...))
-       l.logger.Printf("WARNING: %s", msg)
-}
-
-func (l *grpcLogger) Error(args ...interface{}) {
-       msg := fmt.Sprint(args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Print("ERROR: ", msg)
-}
-
-func (l *grpcLogger) Errorln(args ...interface{}) {
-       msg := fmt.Sprintln(args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Print("ERROR: ", msg)
-}
-
-func (l *grpcLogger) Errorf(format string, args ...interface{}) {
-       msg := fmt.Sprintf(format, args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Printf("ERROR: %s", msg)
-}
-
-func (l *grpcLogger) Fatal(args ...interface{}) {
-       l.logger.Fatal(args...)
-}
-
-func (l *grpcLogger) Fatalln(args ...interface{}) {
-       l.logger.Fatal(args...)
-}
-
-func (l *grpcLogger) Fatalf(format string, args ...interface{}) {
-       l.logger.Fatalf(format, args...)
-}
-
-func (l *grpcLogger) V(level int) bool {
-       return level <= 0
-}
-
-func main() {
-       flag.Parse()
-
-       // Set custom gRPC logger to filter out xDS informational logs
-       // The "ERROR: [xds] Listener entering mode: SERVING" is actually an 
informational log
-       grpclog.SetLoggerV2(&grpcLogger{
-               logger: log.New(os.Stderr, "", log.LstdFlags),
-       })
-
-       go startTestServer(*port)
-
-       log.Printf("Consumer running. Test server listening on port %d for 
ForwardEcho", *port)
-
-       sigChan := make(chan os.Signal, 1)
-       signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
-       <-sigChan
-       log.Println("Shutting down...")
-
-       if testServer != nil {
-               log.Println("Stopping test server...")
-               testServer.GracefulStop()
-       }
-}
-
-func startTestServer(port int) {
-       lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
-       if err != nil {
-               log.Printf("Failed to listen on port %d: %v", port, err)
-               return
-       }
-
-       testServer = grpc.NewServer()
-       pb.RegisterEchoTestServiceServer(testServer, &testServerImpl{
-               connCache: make(map[string]*cachedConnection),
-       })
-       reflection.Register(testServer)
-
-       log.Printf("Test server listening on port %d for ForwardEcho 
(reflection enabled)", port)
-       if err := testServer.Serve(lis); err != nil {
-               log.Printf("Test server error: %v", err)
-       }
-}
-
-type cachedConnection struct {
-       conn      *grpc.ClientConn
-       createdAt time.Time
-}
-
-type testServerImpl struct {
-       pb.UnimplementedEchoTestServiceServer
-       // Connection cache: map from URL to cached connection
-       connCache map[string]*cachedConnection
-       connMutex sync.RWMutex
-}
-
-// formatGRPCError formats gRPC errors similar to grpcurl output
-func formatGRPCError(err error, index int32, total int32) string {
-       if err == nil {
-               return ""
-       }
-
-       // Extract gRPC status code and message using status package
-       code := "Unknown"
-       message := err.Error()
-
-       // Try to extract gRPC status
-       if st, ok := status.FromError(err); ok {
-               code = st.Code().String()
-               message = st.Message()
-       } else {
-               // Fallback: try to extract from error string
-               if strings.Contains(message, "code = ") {
-                       // Extract code like "code = Unavailable"
-                       codeMatch := regexp.MustCompile(`code = 
(\w+)`).FindStringSubmatch(message)
-                       if len(codeMatch) > 1 {
-                               code = codeMatch[1]
-                       }
-                       // Extract message after "desc = "
-                       descMatch := regexp.MustCompile(`desc = 
"?([^"]+)"?`).FindStringSubmatch(message)
-                       if len(descMatch) > 1 {
-                               message = descMatch[1]
-                       } else {
-                               // If no desc, try to extract message after code
-                               parts := strings.SplitN(message, "desc = ", 2)
-                               if len(parts) > 1 {
-                                       message = strings.Trim(parts[1], `"`)
-                               }
-                       }
-               }
-       }
-
-       // Format similar to grpcurl (single line format)
-       if total == 1 {
-               return fmt.Sprintf("ERROR:\nCode: %s\nMessage: %s", code, 
message)
-       }
-       return fmt.Sprintf("[%d] Error: rpc error: code = %s desc = %s", index, 
code, message)
-}
-
-func (s *testServerImpl) ForwardEcho(ctx context.Context, req 
*pb.ForwardEchoRequest) (*pb.ForwardEchoResponse, error) {
-       if req == nil {
-               return nil, fmt.Errorf("request is nil")
-       }
-
-       if req.Url == "" {
-               return nil, fmt.Errorf("url is required")
-       }
-
-       count := req.Count
-       if count < 0 {
-               count = 0
-       }
-       if count > 100 {
-               count = 100
-       }
-
-       log.Printf("ForwardEcho: url=%s, count=%d", req.Url, count)
-
-       // Check bootstrap configuration
-       bootstrapPath := os.Getenv("GRPC_XDS_BOOTSTRAP")
-       if bootstrapPath == "" {
-               return nil, fmt.Errorf("GRPC_XDS_BOOTSTRAP environment variable 
is not set")
-       }
-
-       // Verify bootstrap file exists
-       if _, err := os.Stat(bootstrapPath); os.IsNotExist(err) {
-               return nil, fmt.Errorf("bootstrap file does not exist: %s", 
bootstrapPath)
-       }
-
-       // Read bootstrap file to verify UDS socket
-       bootstrapData, err := os.ReadFile(bootstrapPath)
-       if err != nil {
-               return nil, fmt.Errorf("failed to read bootstrap file: %v", err)
-       }
-
-       var bootstrapJSON map[string]interface{}
-       if err := json.Unmarshal(bootstrapData, &bootstrapJSON); err != nil {
-               return nil, fmt.Errorf("failed to parse bootstrap file: %v", 
err)
-       }
-
-       // Extract UDS socket path
-       var udsPath string
-       if xdsServers, ok := bootstrapJSON["xds_servers"].([]interface{}); ok 
&& len(xdsServers) > 0 {
-               if server, ok := xdsServers[0].(map[string]interface{}); ok {
-                       if serverURI, ok := server["server_uri"].(string); ok {
-                               if strings.HasPrefix(serverURI, "unix://") {
-                                       udsPath = strings.TrimPrefix(serverURI, 
"unix://")
-                                       if _, err := os.Stat(udsPath); 
os.IsNotExist(err) {
-                                               return nil, fmt.Errorf("UDS 
socket does not exist: %s", udsPath)
-                                       }
-                               }
-                       }
-               }
-       }
-
-       // Reuse connections to avoid creating new xDS connections for each RPC 
call
-       // This prevents the RDS request loop issue and ensures stable 
connection state
-       s.connMutex.RLock()
-       cached, exists := s.connCache[req.Url]
-       var conn *grpc.ClientConn
-       if exists && cached != nil {
-               conn = cached.conn
-       }
-       s.connMutex.RUnlock()
-
-       // Check if cached connection is still valid and not too old.
-       // When xDS config changes (e.g., TLS is added/removed), gRPC xDS 
client should update connections,
-       // but if the connection was established before xDS config was 
received, it may use old configuration.
-       // To ensure we use the latest xDS config, we clear connections older 
than 10 seconds.
-       // rebuilt quickly to use the new configuration.
-       const maxConnectionAge = 10 * time.Second
-       if exists && conn != nil {
-               state := conn.GetState()
-               if state == connectivity.Shutdown {
-                       // Connection is closed, remove from cache
-                       log.Printf("ForwardEcho: cached connection for %s is 
SHUTDOWN, removing from cache", req.Url)
-                       s.connMutex.Lock()
-                       delete(s.connCache, req.Url)
-                       conn = nil
-                       exists = false
-                       s.connMutex.Unlock()
-               } else if time.Since(cached.createdAt) > maxConnectionAge {
-                       // Connection is too old, may be using stale xDS config 
(e.g., plaintext when TLS is required)
-                       // Clear cache to force reconnection with latest xDS 
config
-                       log.Printf("ForwardEcho: cached connection for %s is 
too old (%v), clearing cache to use latest xDS config", req.Url, 
time.Since(cached.createdAt))
-                       s.connMutex.Lock()
-                       if cachedConn, stillExists := s.connCache[req.Url]; 
stillExists && cachedConn != nil && cachedConn.conn != nil {
-                               cachedConn.conn.Close()
-                       }
-                       delete(s.connCache, req.Url)
-                       conn = nil
-                       exists = false
-                       s.connMutex.Unlock()
-               }
-       }
-
-       if !exists || conn == nil {
-               // Create new connection
-               s.connMutex.Lock()
-               // Double-check after acquiring write lock
-               if cached, exists = s.connCache[req.Url]; !exists || cached == 
nil || cached.conn == nil {
-                       conn = nil
-                       // When TLS is configured (DestinationRule 
ISTIO_MUTUAL), gRPC xDS client needs
-                       // to fetch certificates from CertificateProvider. The 
CertificateProvider uses file_watcher
-                       // to read certificate files. If the files are not 
ready or CertificateProvider is not
-                       // initialized, certificate fetching will timeout.
-                       // We wait a short time to ensure CertificateProvider 
is ready and certificate files are accessible.
-                       // This is especially important when DestinationRule is 
just created and TLS is enabled.
-                       // The CertificateProvider may need time to initialize, 
especially on first connection.
-                       // We wait 3 seconds to give CertificateProvider enough 
time to initialize (reduced from 5s for faster startup).
-                       log.Printf("ForwardEcho: waiting 3 seconds to ensure 
CertificateProvider is ready...")
-                       time.Sleep(3 * time.Second)
-
-                       // Create xDS client credentials
-                       // NOTE: FallbackCreds is REQUIRED by gRPC xDS library 
for initial connection
-                       // before xDS configuration is available. However, once 
xDS configures TLS,
-                       // the client will use TLS and will NOT fallback to 
plaintext if TLS fails.
-                       // FallbackCreds is only used when xDS has not yet 
provided TLS configuration.
-                       creds, err := 
xdscreds.NewClientCredentials(xdscreds.ClientOptions{
-                               FallbackCreds: insecure.NewCredentials(),
-                       })
-                       if err != nil {
-                               s.connMutex.Unlock()
-                               return nil, fmt.Errorf("failed to create xDS 
client credentials: %v", err)
-                       }
-
-                       // Dial with xDS URL - use background context, not the 
request context
-                       // The request context might timeout before xDS 
configuration is received
-                       // When TLS is configured (DestinationRule 
ISTIO_MUTUAL), gRPC xDS client needs
-                       // to fetch certificates from CertificateProvider. This 
may take time, especially on
-                       // first connection. We use a longer timeout context to 
allow certificate fetching.
-                       log.Printf("ForwardEcho: creating new connection for 
%s...", req.Url)
-                       dialCtx, dialCancel := 
context.WithTimeout(context.Background(), 60*time.Second)
-                       conn, err = grpc.DialContext(dialCtx, req.Url, 
grpc.WithTransportCredentials(creds))
-                       dialCancel()
-                       if err != nil {
-                               s.connMutex.Unlock()
-                               return nil, fmt.Errorf("failed to dial %s: %v", 
req.Url, err)
-                       }
-                       s.connCache[req.Url] = &cachedConnection{
-                               conn:      conn,
-                               createdAt: time.Now(),
-                       }
-                       log.Printf("ForwardEcho: cached connection for %s", 
req.Url)
-               }
-               s.connMutex.Unlock()
-       } else {
-               log.Printf("ForwardEcho: reusing cached connection for %s 
(state: %v)", req.Url, conn.GetState())
-               // NOTE: We reuse the cached connection. If xDS config changes 
(e.g., TLS is added/removed),
-               // gRPC xDS client should automatically update the connection. 
However, if the connection
-               // was established before xDS config was received, it may still 
be using old configuration.
-               // We rely on the TLS mismatch detection logic in the RPC error 
handling to handle this case.
-       }
-
-       initialState := conn.GetState()
-       log.Printf("ForwardEcho: initial connection state: %v", initialState)
-
-       // Even if connection is READY, we need to verify it's still valid
-       // because xDS configuration may have changed (e.g., from plaintext to 
TLS)
-       // and the cached connection might be using old configuration.
-       // gRPC xDS client should automatically update connections, but if the 
connection
-       // was established before xDS config was received, it might be using 
FallbackCreds (plaintext).
-       // We'll proceed with RPC calls, but if they fail with TLS/plaintext 
mismatch errors,
-       // we'll clear the cache and retry.
-       // When TLS is configured (DestinationRule ISTIO_MUTUAL), gRPC xDS 
client needs
-       // to fetch certificates from CertificateProvider during TLS handshake. 
The TLS handshake
-       // happens when the connection state transitions to READY. If 
CertificateProvider is not ready,
-       // the TLS handshake will timeout. We need to wait for the connection 
to be READY, which
-       // indicates that TLS handshake has completed successfully.
-       if initialState == connectivity.Ready {
-               log.Printf("ForwardEcho: connection is already READY, 
proceeding with RPC calls (will retry with new connection if TLS/plaintext 
mismatch detected)")
-       } else {
-               // Only wait for new connections or connections that are not 
READY
-               // For gRPC xDS proxyless, we need to wait for the client to 
receive and process LDS/CDS/EDS
-               // The connection state may transition: IDLE -> CONNECTING -> 
READY (or TRANSIENT_FAILURE -> CONNECTING -> READY)
-               // When TLS is configured, the TLS handshake happens during 
this state transition.
-               // If CertificateProvider is not ready, the TLS handshake will 
timeout and connection will fail.
-               // We use a longer timeout (60 seconds) to allow 
CertificateProvider to fetch certificates.
-               log.Printf("ForwardEcho: waiting for xDS configuration to be 
processed and connection to be ready (60 seconds)...")
-
-               // Wait for state changes with multiple attempts
-               maxWait := 60 * time.Second
-
-               // Wait for state changes, allowing multiple state transitions
-               // Don't exit on TRANSIENT_FAILURE - it may recover to READY
-               stateChanged := false
-               currentState := initialState
-               startTime := time.Now()
-               lastStateChangeTime := startTime
-
-               for time.Since(startTime) < maxWait {
-                       if currentState == connectivity.Ready {
-                               log.Printf("ForwardEcho: connection is READY 
after %v", time.Since(startTime))
-                               stateChanged = true
-                               break
-                       }
-
-                       // Only exit on Shutdown, not on TransientFailure (it 
may recover)
-                       if currentState == connectivity.Shutdown {
-                               log.Printf("ForwardEcho: connection in %v state 
after %v, cannot recover", currentState, time.Since(startTime))
-                               break
-                       }
-
-                       // Wait for state change with remaining timeout
-                       remaining := maxWait - time.Since(startTime)
-                       if remaining <= 0 {
-                               break
-                       }
-
-                       // Use shorter timeout for each WaitForStateChange call 
to allow periodic checks
-                       waitTimeout := remaining
-                       if waitTimeout > 5*time.Second {
-                               waitTimeout = 5 * time.Second
-                       }
-
-                       stateCtx, stateCancel := 
context.WithTimeout(context.Background(), waitTimeout)
-                       if conn.WaitForStateChange(stateCtx, currentState) {
-                               newState := conn.GetState()
-                               elapsed := time.Since(startTime)
-                               log.Printf("ForwardEcho: connection state 
changed from %v to %v after %v", currentState, newState, elapsed)
-                               stateChanged = true
-                               currentState = newState
-                               lastStateChangeTime = time.Now()
-
-                               // If READY, we're done
-                               if newState == connectivity.Ready {
-                                       stateCancel()
-                                       break
-                               }
-
-                               // If we're in TRANSIENT_FAILURE, continue 
waiting - it may recover
-                               // gRPC xDS client will retry connection when 
endpoints become available
-                               if newState == connectivity.TransientFailure {
-                                       log.Printf("ForwardEcho: connection in 
TRANSIENT_FAILURE, continuing to wait for recovery (remaining: %v)", 
maxWait-elapsed)
-                               }
-                       } else {
-                               // Timeout waiting for state change - check if 
we should continue
-                               elapsed := time.Since(startTime)
-                               if currentState == 
connectivity.TransientFailure {
-                                       // If we've been in TRANSIENT_FAILURE 
for a while, continue waiting
-                                       // The connection may recover when 
endpoints become available
-                                       if time.Since(lastStateChangeTime) < 
10*time.Second {
-                                               log.Printf("ForwardEcho: still 
in TRANSIENT_FAILURE after %v, continuing to wait (remaining: %v)", elapsed, 
maxWait-elapsed)
-                                       } else {
-                                               log.Printf("ForwardEcho: no 
state change after %v, current state: %v (remaining: %v)", elapsed, 
currentState, maxWait-elapsed)
-                                       }
-                               } else {
-                                       log.Printf("ForwardEcho: no state 
change after %v, current state: %v (remaining: %v)", elapsed, currentState, 
maxWait-elapsed)
-                               }
-                       }
-                       stateCancel()
-               }
-
-               finalState := conn.GetState()
-               log.Printf("ForwardEcho: final connection state: %v 
(stateChanged=%v, waited=%v)", finalState, stateChanged, time.Since(startTime))
-
-               // If connection is not READY, log a warning but proceed anyway
-               // The first RPC call may trigger connection establishment
-               if finalState != connectivity.Ready {
-                       log.Printf("ForwardEcho: WARNING - connection is not 
READY (state=%v), but proceeding with RPC calls", finalState)
-               }
-       }
-
-       // Create client and make RPC calls
-       client := pb.NewEchoServiceClient(conn)
-       output := make([]string, 0, count)
-
-       log.Printf("ForwardEcho: sending %d requests...", count)
-       errorCount := 0
-       firstError := ""
-       for i := int32(0); i < count; i++ {
-               echoReq := &pb.EchoRequest{
-                       Message: fmt.Sprintf("Request %d", i+1),
-               }
-
-               currentState := conn.GetState()
-               log.Printf("ForwardEcho: sending request %d (connection state: 
%v)...", i+1, currentState)
-
-               // Use longer timeout for requests to allow TLS handshake 
completion
-               // When mTLS is configured, certificate fetching and TLS 
handshake may take time
-               // Use 30 seconds for all requests to ensure TLS handshake has 
enough time
-               timeout := 30 * time.Second
-
-               reqCtx, reqCancel := context.WithTimeout(context.Background(), 
timeout)
-               reqStartTime := time.Now()
-               resp, err := client.Echo(reqCtx, echoReq)
-               duration := time.Since(reqStartTime)
-               reqCancel()
-
-               // Check connection state after RPC call
-               stateAfterRPC := conn.GetState()
-               log.Printf("ForwardEcho: request %d completed in %v, connection 
state: %v (was %v)", i+1, duration, stateAfterRPC, currentState)
-
-               if err != nil {
-                       log.Printf("ForwardEcho: request %d failed: %v", i+1, 
err)
-                       errorCount++
-                       if firstError == "" {
-                               firstError = err.Error()
-                       }
-
-                       // Format error similar to grpcurl output
-                       errMsg := formatGRPCError(err, i, count)
-                       output = append(output, errMsg)
-
-                       // Only clear cache if we detect specific TLS/plaintext 
mismatch errors.
-                       // TRANSIENT_FAILURE can occur for many reasons (e.g., 
xDS config updates, endpoint changes),
-                       // so we should NOT clear cache on every 
TRANSIENT_FAILURE.
-                       // Only clear cache when we detect explicit TLS-related 
errors that indicate a mismatch.
-                       errStr := err.Error()
-                       isTLSMismatch := false
-
-                       // Check for specific TLS/plaintext mismatch indicators:
-                       // - "tls: first record does not look like a TLS 
handshake" - client uses TLS but server is plaintext
-                       // - "authentication handshake failed" with TLS context 
- TLS handshake failed
-                       // - "context deadline exceeded" during authentication 
handshake - may indicate TLS handshake timeout
-                       //   (e.g., client using plaintext but server requiring 
TLS, or vice versa)
-                       // - "fetching trusted roots from CertificateProvider 
failed" - CertificateProvider not ready yet
-                       //   This is a temporary error that should be retried 
after waiting for CertificateProvider to be ready
-                       // These errors indicate that client and server TLS 
configuration are mismatched, or CertificateProvider is not ready
-                       if strings.Contains(errStr, "tls: first record does not 
look like a TLS handshake") ||
-                               (strings.Contains(errStr, "authentication 
handshake failed") && strings.Contains(errStr, "tls:")) ||
-                               (strings.Contains(errStr, "authentication 
handshake failed") && strings.Contains(errStr, "context deadline exceeded")) ||
-                               strings.Contains(errStr, "fetching trusted 
roots from CertificateProvider failed") {
-                               isTLSMismatch = true
-                               log.Printf("ForwardEcho: detected TLS/plaintext 
mismatch or CertificateProvider not ready error: %v", err)
-                       }
-
-                       // When TLS mismatch is detected, immediately clear 
cache and force reconnection.
-                       // This ensures that:
-                       // 1. If client config changed (plaintext -> TLS), new 
connection uses TLS
-                       // 2. If server config changed (TLS -> plaintext), new 
connection uses plaintext
-                       // 3. Connection behavior is consistent with current 
xDS configuration
-                       // - When only client TLS (DestinationRule 
ISTIO_MUTUAL) but server plaintext: connection SHOULD FAIL
-                       // - When client TLS + server mTLS (PeerAuthentication 
STRICT): connection SHOULD SUCCEED
-                       // - When both plaintext: connection SHOULD SUCCEED
-                       // By clearing cache and reconnecting, we ensure 
connection uses current xDS config.
-                       if isTLSMismatch {
-                               // Check if this is a CertificateProvider not 
ready error (temporary) vs configuration mismatch (persistent)
-                               isCertProviderNotReady := 
strings.Contains(errStr, "fetching trusted roots from CertificateProvider 
failed")
-
-                               // Clear cache and force reconnection on every 
TLS mismatch detection
-                               // This ensures we always try to use the latest 
xDS configuration
-                               if isCertProviderNotReady {
-                                       log.Printf("ForwardEcho: WARNING - 
CertificateProvider not ready yet: %v", err)
-                                       log.Printf("ForwardEcho: NOTE - This is 
a temporary error. CertificateProvider needs time to initialize.")
-                                       log.Printf("ForwardEcho: Clearing 
connection cache and waiting for CertificateProvider to be ready...")
-                               } else {
-                                       log.Printf("ForwardEcho: WARNING - 
detected TLS/plaintext mismatch error: %v", err)
-                                       log.Printf("ForwardEcho: NOTE - This 
error indicates that client and server TLS configuration are mismatched")
-                                       log.Printf("ForwardEcho: This usually 
happens when:")
-                                       log.Printf("ForwardEcho:   1. 
DestinationRule with ISTIO_MUTUAL exists but PeerAuthentication with STRICT 
does not (client TLS, server plaintext)")
-                                       log.Printf("ForwardEcho:   2. 
DestinationRule was deleted but cached connection still uses TLS")
-                                       log.Printf("ForwardEcho: Clearing 
connection cache to force reconnection with updated xDS config...")
-                               }
-
-                               s.connMutex.Lock()
-                               if cachedConn, stillExists := 
s.connCache[req.Url]; stillExists && cachedConn != nil && cachedConn.conn != 
nil {
-                                       cachedConn.conn.Close()
-                               }
-                               delete(s.connCache, req.Url)
-                               conn = nil
-                               s.connMutex.Unlock()
-
-                               // Wait for xDS config to propagate and be 
processed by gRPC xDS client.
-                               // When CDS/LDS config changes, it takes time 
for:
-                               // 1. Control plane to push new config to gRPC 
xDS client
-                               // 2. gRPC xDS client to process and apply new 
config
-                               // 3. CertificateProvider to be ready and 
certificate files to be accessible
-                               // 4. New connections to use updated config
-                               // For CertificateProvider not ready errors, we 
wait shorter time (3 seconds) as it's usually faster.
-                               // For configuration mismatch, we wait longer 
(10 seconds) to ensure config has propagated.
-                               // Reduced wait times for faster recovery while 
still ensuring reliability.
-                               if isCertProviderNotReady {
-                                       log.Printf("ForwardEcho: waiting 3 
seconds for CertificateProvider to be ready...")
-                                       time.Sleep(3 * time.Second)
-                               } else {
-                                       log.Printf("ForwardEcho: waiting 10 
seconds for xDS config to propagate and CertificateProvider to be ready...")
-                                       time.Sleep(10 * time.Second)
-                               }
-
-                               // Recreate connection - this will use current 
xDS config
-                               // When TLS is configured, gRPC xDS client 
needs to fetch certificates
-                               // from CertificateProvider. Use a longer 
timeout to allow certificate fetching.
-                               log.Printf("ForwardEcho: recreating connection 
with current xDS config...")
-                               creds, credErr := 
xdscreds.NewClientCredentials(xdscreds.ClientOptions{
-                                       FallbackCreds: 
insecure.NewCredentials(),
-                               })
-                               if credErr != nil {
-                                       log.Printf("ForwardEcho: failed to 
create xDS client credentials: %v", credErr)
-                                       // Continue to record error
-                               } else {
-                                       // Wait additional time before dialing 
to ensure CertificateProvider is ready
-                                       // Reduced from 3s to 2s for faster 
recovery
-                                       log.Printf("ForwardEcho: waiting 2 
seconds before dialing to ensure CertificateProvider is ready...")
-                                       time.Sleep(2 * time.Second)
-
-                                       dialCtx, dialCancel := 
context.WithTimeout(context.Background(), 60*time.Second)
-                                       newConn, dialErr := 
grpc.DialContext(dialCtx, req.Url, grpc.WithTransportCredentials(creds))
-                                       dialCancel()
-                                       if dialErr != nil {
-                                               log.Printf("ForwardEcho: failed 
to dial %s: %v", req.Url, dialErr)
-                                               // Continue to record error
-                                       } else {
-                                               s.connMutex.Lock()
-                                               s.connCache[req.Url] = 
&cachedConnection{
-                                                       conn:      newConn,
-                                                       createdAt: time.Now(),
-                                               }
-                                               conn = newConn
-                                               client = 
pb.NewEchoServiceClient(conn)
-                                               s.connMutex.Unlock()
-                                               log.Printf("ForwardEcho: 
connection recreated, retrying request %d", i+1)
-                                               // Retry this request with new 
connection
-                                               continue
-                                       }
-                               }
-                       }
-                       if i < count-1 {
-                               waitTime := 2 * time.Second
-                               log.Printf("ForwardEcho: waiting %v before next 
request...", waitTime)
-                               time.Sleep(waitTime)
-                       }
-                       continue
-               }
-
-               if resp == nil {
-                       log.Printf("ForwardEcho: request %d failed: response is 
nil", i+1)
-                       output = append(output, fmt.Sprintf("[%d] Error: 
response is nil", i))
-                       continue
-               }
-
-               log.Printf("ForwardEcho: request %d succeeded: Hostname=%s 
ServiceVersion=%s Namespace=%s IP=%s",
-                       i+1, resp.Hostname, resp.ServiceVersion, 
resp.Namespace, resp.Ip)
-
-               lineParts := []string{
-                       fmt.Sprintf("[%d body] Hostname=%s", i, resp.Hostname),
-               }
-               if resp.ServiceVersion != "" {
-                       lineParts = append(lineParts, 
fmt.Sprintf("ServiceVersion=%s", resp.ServiceVersion))
-               }
-               if resp.Namespace != "" {
-                       lineParts = append(lineParts, 
fmt.Sprintf("Namespace=%s", resp.Namespace))
-               }
-               if resp.Ip != "" {
-                       lineParts = append(lineParts, fmt.Sprintf("IP=%s", 
resp.Ip))
-               }
-               if resp.Cluster != "" {
-                       lineParts = append(lineParts, fmt.Sprintf("Cluster=%s", 
resp.Cluster))
-               }
-               if resp.ServicePort > 0 {
-                       lineParts = append(lineParts, 
fmt.Sprintf("ServicePort=%d", resp.ServicePort))
-               }
-
-               output = append(output, strings.Join(lineParts, " "))
-
-               // Small delay between successful requests to avoid 
overwhelming the server
-               if i < count-1 {
-                       time.Sleep(100 * time.Millisecond)
-               }
-       }
-
-       log.Printf("ForwardEcho: completed %d requests", count)
-
-       // If all requests failed, add summary similar to grpcurl
-       if errorCount > 0 && errorCount == int(count) && firstError != "" {
-               summary := fmt.Sprintf("ERROR:\nCode: Unknown\nMessage: %d/%d 
requests had errors; first error: %s", errorCount, count, firstError)
-               // Prepend summary to output
-               output = append([]string{summary}, output...)
-       }
-
-       return &pb.ForwardEchoResponse{
-               Output: output,
-       }, nil
-}
diff --git a/tests/grpc-app/docker/dockerfile.consumer 
b/tests/grpc-app/docker/dockerfile.consumer
deleted file mode 100644
index 8c4def20..00000000
--- a/tests/grpc-app/docker/dockerfile.consumer
+++ /dev/null
@@ -1,53 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
-FROM golang:1.24-alpine AS builder
-
-WORKDIR /build
-
-RUN apk add --no-cache protobuf
-
-COPY go.mod go.sum ./
-RUN go mod download
-
-RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
-    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
-
-ENV PATH=$PATH:/go/bin:/root/go/bin
-
-COPY proto/echo.proto ./proto/
-RUN protoc --go_out=. --go_opt=paths=source_relative \
-    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
-    proto/echo.proto
-
-COPY consumer/ ./consumer/
-
-ARG GOOS=linux
-ARG GOARCH=amd64
-RUN GOOS=${GOOS} GOARCH=${GOARCH} CGO_ENABLED=0 go build -a -ldflags 
'-extldflags "-static"' -o /build/grpc-consumer ./consumer/
-
-FROM alpine:latest
-RUN apk update && \
-    apk --no-cache add ca-certificates tzdata || \
-    (sleep 2 && apk update && apk --no-cache add ca-certificates tzdata)
-WORKDIR /app
-
-COPY --from=builder /build/grpc-consumer /usr/local/bin/grpc-consumer
-RUN chmod +x /usr/local/bin/grpc-consumer
-
-COPY ./grpcurl /usr/local/bin/grpcurl
-RUN chmod +x /usr/local/bin/grpcurl
-
-ENTRYPOINT ["/usr/local/bin/grpc-consumer"]
diff --git a/tests/grpc-app/docker/dockerfile.provider 
b/tests/grpc-app/docker/dockerfile.provider
deleted file mode 100644
index 58945080..00000000
--- a/tests/grpc-app/docker/dockerfile.provider
+++ /dev/null
@@ -1,50 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
-FROM golang:1.24-alpine AS builder
-
-WORKDIR /build
-
-RUN apk add --no-cache protobuf
-
-COPY go.mod go.sum ./
-RUN go mod download
-
-RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
-    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
-
-ENV PATH=$PATH:/go/bin:/root/go/bin
-
-COPY proto/echo.proto ./proto/
-RUN protoc --go_out=. --go_opt=paths=source_relative \
-    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
-    proto/echo.proto
-
-COPY provider/ ./provider/
-
-ARG GOOS=linux
-ARG GOARCH=amd64
-RUN GOOS=${GOOS} GOARCH=${GOARCH} CGO_ENABLED=0 go build -a -ldflags 
'-extldflags "-static"' -o /build/grpc-provider ./provider/
-
-FROM alpine:latest
-RUN apk update && \
-    apk --no-cache add ca-certificates tzdata || \
-    (sleep 2 && apk update && apk --no-cache add ca-certificates tzdata)
-WORKDIR /app
-
-COPY --from=builder /build/grpc-provider /usr/local/bin/grpc-provider
-RUN chmod +x /usr/local/bin/grpc-provider
-
-ENTRYPOINT ["/usr/local/bin/grpc-provider"]
diff --git a/tests/grpc-app/generate-proto.sh b/tests/grpc-app/generate-proto.sh
deleted file mode 100755
index 1657f6da..00000000
--- a/tests/grpc-app/generate-proto.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
-set -e
-cd "$(dirname "$0")"
-
-# This script will be used when protoc is fixed
-# For now, proto files are generated in Dockerfile
-echo "Proto files are generated in Dockerfile during build"
-echo "To generate locally, fix protoc first:"
-echo "  brew reinstall protobuf abseil"
-echo ""
-echo "Then run:"
-echo "  protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. 
--go-grpc_opt=paths=source_relative proto/echo.proto"
diff --git a/tests/grpc-app/go.mod b/tests/grpc-app/go.mod
deleted file mode 100644
index b14c730f..00000000
--- a/tests/grpc-app/go.mod
+++ /dev/null
@@ -1,44 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You 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.
-
-module github.com/apache/dubbo-kubernetes/tests/grpc-app
-
-go 1.24.0
-
-require (
-       google.golang.org/grpc v1.76.0
-       google.golang.org/protobuf v1.36.10
-)
-
-require (
-       cel.dev/expr v0.24.0 // indirect
-       cloud.google.com/go/compute/metadata v0.7.0 // indirect
-       github.com/cespare/xxhash/v2 v2.3.0 // indirect
-       github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
-       github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
-       github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
-       github.com/go-jose/go-jose/v4 v4.1.2 // indirect
-       github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 
// indirect
-       github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
-       github.com/zeebo/errs v1.4.0 // indirect
-       golang.org/x/crypto v0.43.0 // indirect
-       golang.org/x/net v0.46.0 // indirect
-       golang.org/x/oauth2 v0.30.0 // indirect
-       golang.org/x/sync v0.17.0 // indirect
-       golang.org/x/sys v0.37.0 // indirect
-       golang.org/x/text v0.30.0 // indirect
-       google.golang.org/genproto/googleapis/api 
v0.0.0-20250804133106-a7a43d27e69b // indirect
-       google.golang.org/genproto/googleapis/rpc 
v0.0.0-20251103181224-f26f9409b101 // indirect
-)
diff --git a/tests/grpc-app/go.sum b/tests/grpc-app/go.sum
deleted file mode 100644
index a0ddf76c..00000000
--- a/tests/grpc-app/go.sum
+++ /dev/null
@@ -1,76 +0,0 @@
-cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
-cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
-cloud.google.com/go/compute/metadata v0.7.0 
h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
-cloud.google.com/go/compute/metadata v0.7.0/go.mod 
h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
-github.com/cespare/xxhash/v2 v2.3.0 
h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
-github.com/cespare/xxhash/v2 v2.3.0/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 
h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
-github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod 
h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
-github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/envoyproxy/go-control-plane v0.13.4 
h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
-github.com/envoyproxy/go-control-plane v0.13.4/go.mod 
h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4 
h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod 
h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
-github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 
h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
-github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod 
h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
-github.com/envoyproxy/protoc-gen-validate v1.2.1 
h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
-github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod 
h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
-github.com/go-jose/go-jose/v4 v4.1.2 
h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
-github.com/go-jose/go-jose/v4 v4.1.2/go.mod 
h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod 
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod 
h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/golang/protobuf v1.5.4 
h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod 
h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod 
h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod 
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 
h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod 
h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
-github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/spiffe/go-spiffe/v2 v2.5.0 
h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
-github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod 
h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
-github.com/stretchr/testify v1.10.0 
h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
-github.com/zeebo/errs v1.4.0/go.mod 
h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
-go.opentelemetry.io/auto/sdk v1.1.0 
h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod 
h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/otel v1.37.0 
h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod 
h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 
h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod 
h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 
h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod 
h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 
h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod 
h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 
h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod 
h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod 
h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
-golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
-golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
-golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
-golang.org/x/oauth2 v0.30.0/go.mod 
h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod 
h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod 
h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
-gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
-gonum.org/v1/gonum v0.16.0/go.mod 
h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b 
h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
-google.golang.org/genproto/googleapis/api 
v0.0.0-20250804133106-a7a43d27e69b/go.mod 
h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 
h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
-google.golang.org/genproto/googleapis/rpc 
v0.0.0-20251103181224-f26f9409b101/go.mod 
h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod 
h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
-google.golang.org/protobuf v1.36.10 
h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
-google.golang.org/protobuf v1.36.10/go.mod 
h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tests/grpc-app/proto/echo.proto b/tests/grpc-app/proto/echo.proto
deleted file mode 100644
index 1d7c32e6..00000000
--- a/tests/grpc-app/proto/echo.proto
+++ /dev/null
@@ -1,62 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You 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";
-
-package echo;
-
-option go_package = "github.com/apache/dubbo-kubernetes/tests/grpc-app/proto";
-
-// Echo service for testing gRPC proxyless
-service EchoService {
-  // Echo returns the same message sent
-  rpc Echo(EchoRequest) returns (EchoResponse);
-  
-  // StreamEcho streams back the same message
-  rpc StreamEcho(EchoRequest) returns (stream EchoResponse);
-}
-
-// EchoTestService for grpcurl testing (similar to Istio's echo service)
-service EchoTestService {
-  // ForwardEcho forwards a request to another service and returns the response
-  rpc ForwardEcho(ForwardEchoRequest) returns (ForwardEchoResponse);
-}
-
-message EchoRequest {
-  string message = 1;
-}
-
-message EchoResponse {
-  string message = 1;
-  string hostname = 2;
-  string service_version = 3;
-  string namespace = 4;
-  string ip = 5;
-  string cluster = 6;
-  int32 service_port = 7;
-}
-
-message ForwardEchoRequest {
-  string url = 1;  // Target URL (e.g., 
"xds:///consumer.grpc-app.svc.cluster.local:7070")
-  int32 count = 2; // Number of requests to send
-  map<string, string> headers = 3;
-  int32 timeout = 4; // Timeout in seconds
-  bool h2 = 5; // Use HTTP/2
-  bool insecure = 6; // Use insecure connection
-}
-
-message ForwardEchoResponse {
-  repeated string output = 1; // Output lines from the requests
-}
diff --git a/tests/grpc-app/proto/gen.sh b/tests/grpc-app/proto/gen.sh
deleted file mode 100755
index fc0bea40..00000000
--- a/tests/grpc-app/proto/gen.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-set -e
-cd "$(dirname "$0")/.."
-export PATH=$PATH:$(go env GOPATH)/bin
-protoc --go_out=. --go_opt=paths=source_relative \
-       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
-       proto/echo.proto
diff --git a/tests/grpc-app/provider/main.go b/tests/grpc-app/provider/main.go
deleted file mode 100644
index c3d222b8..00000000
--- a/tests/grpc-app/provider/main.go
+++ /dev/null
@@ -1,404 +0,0 @@
-//
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You 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 main
-
-import (
-       "context"
-       "flag"
-       "fmt"
-       "log"
-       "net"
-       "os"
-       "os/signal"
-       "regexp"
-       "strconv"
-       "strings"
-       "syscall"
-       "time"
-
-       "google.golang.org/grpc"
-       "google.golang.org/grpc/credentials/insecure"
-       xdscreds "google.golang.org/grpc/credentials/xds"
-       "google.golang.org/grpc/grpclog"
-       "google.golang.org/grpc/reflection"
-       "google.golang.org/grpc/xds"
-
-       pb "github.com/apache/dubbo-kubernetes/tests/grpc-app/proto"
-)
-
-var (
-       port = flag.Int("port", 17070, "gRPC server port")
-)
-
-type echoServer struct {
-       pb.UnimplementedEchoServiceServer
-       pb.UnimplementedEchoTestServiceServer
-       hostname       string
-       serviceVersion string
-       namespace      string
-       instanceIP     string
-       cluster        string
-       servicePort    int
-}
-
-func (s *echoServer) Echo(ctx context.Context, req *pb.EchoRequest) 
(*pb.EchoResponse, error) {
-       if req == nil {
-               return nil, fmt.Errorf("request is nil")
-       }
-       log.Printf("Received: %v", req.Message)
-       return &pb.EchoResponse{
-               Message:        req.Message,
-               Hostname:       s.hostname,
-               ServiceVersion: s.serviceVersion,
-               Namespace:      s.namespace,
-               Ip:             s.instanceIP,
-               Cluster:        s.cluster,
-               ServicePort:    int32(s.servicePort),
-       }, nil
-}
-
-func (s *echoServer) StreamEcho(req *pb.EchoRequest, stream 
pb.EchoService_StreamEchoServer) error {
-       if req == nil {
-               return fmt.Errorf("request is nil")
-       }
-       if stream == nil {
-               return fmt.Errorf("stream is nil")
-       }
-       log.Printf("StreamEcho received: %v", req.Message)
-       for i := 0; i < 3; i++ {
-               if err := stream.Send(&pb.EchoResponse{
-                       Message:  fmt.Sprintf("%s [%d]", req.Message, i),
-                       Hostname: s.hostname,
-               }); err != nil {
-                       log.Printf("StreamEcho send error: %v", err)
-                       return err
-               }
-       }
-       return nil
-}
-
-func (s *echoServer) ForwardEcho(ctx context.Context, req 
*pb.ForwardEchoRequest) (*pb.ForwardEchoResponse, error) {
-       if req == nil {
-               return nil, fmt.Errorf("request is nil")
-       }
-
-       count := req.Count
-       if count < 0 {
-               count = 0
-       }
-       if count > 100 {
-               count = 100
-       }
-
-       log.Printf("ForwardEcho called: url=%s, count=%d", req.Url, count)
-
-       output := make([]string, 0, count)
-       for i := int32(0); i < count; i++ {
-               line := fmt.Sprintf("[%d body] Hostname=%s ServiceVersion=%s 
ServicePort=%d Namespace=%s",
-                       i, s.hostname, s.serviceVersion, s.servicePort, 
s.namespace)
-               if s.instanceIP != "" {
-                       line += fmt.Sprintf(" IP=%s", s.instanceIP)
-               }
-               if s.cluster != "" {
-                       line += fmt.Sprintf(" Cluster=%s", s.cluster)
-               }
-               output = append(output, line)
-       }
-
-       return &pb.ForwardEchoResponse{
-               Output: output,
-       }, nil
-}
-
-// grpcLogger filters out xDS informational logs that are incorrectly marked 
as ERROR
-type grpcLogger struct {
-       logger *log.Logger
-}
-
-var (
-       // Regex to match gRPC formatting errors like %!p(...)
-       formatErrorRegex = regexp.MustCompile(`%!p\([^)]+\)`)
-)
-
-// cleanMessage removes formatting errors from gRPC logs
-// Fixes issues like: 
"\u003c%!p(networktype.keyType=grpc.internal.transport.networktype)\u003e": 
"unix"
-func cleanMessage(msg string) string {
-       // Replace %!p(...) patterns with a cleaner representation
-       msg = formatErrorRegex.ReplaceAllStringFunc(msg, func(match string) 
string {
-               // Extract the key from %!p(networktype.keyType=...)
-               if strings.Contains(match, "networktype.keyType") {
-                       return `"networktype"`
-               }
-               // For other cases, just remove the error pattern
-               return ""
-       })
-       // Also clean up Unicode escape sequences that appear with formatting 
errors
-       // Replace \u003c (which is <) and \u003e (which is >) when they appear 
with formatting errors
-       msg = strings.ReplaceAll(msg, `\u003c`, "<")
-       msg = strings.ReplaceAll(msg, `\u003e`, ">")
-       // Clean up patterns like <...>: "unix" to just show the value
-       msg = regexp.MustCompile(`<[^>]*>:\s*"unix"`).ReplaceAllString(msg, 
`"networktype": "unix"`)
-       return msg
-}
-
-func (l *grpcLogger) Info(args ...interface{}) {
-       msg := fmt.Sprint(args...)
-       // Filter out xDS "entering mode: SERVING" logs
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Print("INFO: ", msg)
-}
-
-func (l *grpcLogger) Infoln(args ...interface{}) {
-       msg := fmt.Sprintln(args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Print("INFO: ", msg)
-}
-
-func (l *grpcLogger) Infof(format string, args ...interface{}) {
-       msg := fmt.Sprintf(format, args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Printf("INFO: %s", msg)
-}
-
-func (l *grpcLogger) Warning(args ...interface{}) {
-       msg := cleanMessage(fmt.Sprint(args...))
-       l.logger.Print("WARNING: ", msg)
-}
-
-func (l *grpcLogger) Warningln(args ...interface{}) {
-       msg := cleanMessage(fmt.Sprintln(args...))
-       l.logger.Print("WARNING: ", msg)
-}
-
-func (l *grpcLogger) Warningf(format string, args ...interface{}) {
-       msg := cleanMessage(fmt.Sprintf(format, args...))
-       l.logger.Printf("WARNING: %s", msg)
-}
-
-func (l *grpcLogger) Error(args ...interface{}) {
-       msg := fmt.Sprint(args...)
-       // Filter out xDS "entering mode: SERVING" logs that are incorrectly 
marked as ERROR
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       // Filter out common connection reset errors - these are normal network 
behavior
-       // when clients disconnect before completing the HTTP/2 handshake
-       if strings.Contains(msg, "connection reset by peer") ||
-               strings.Contains(msg, "failed to receive the preface from 
client") ||
-               strings.Contains(msg, "connection error") {
-               // These are normal network events, log at DEBUG level instead 
of ERROR
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Print("ERROR: ", msg)
-}
-
-func (l *grpcLogger) Errorln(args ...interface{}) {
-       msg := fmt.Sprintln(args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       // Filter out common connection reset errors - these are normal network 
behavior
-       if strings.Contains(msg, "connection reset by peer") ||
-               strings.Contains(msg, "failed to receive the preface from 
client") ||
-               strings.Contains(msg, "connection error") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Print("ERROR: ", msg)
-}
-
-func (l *grpcLogger) Errorf(format string, args ...interface{}) {
-       msg := fmt.Sprintf(format, args...)
-       if strings.Contains(msg, "entering mode") && strings.Contains(msg, 
"SERVING") {
-               return
-       }
-       // Filter out common connection reset errors - these are normal network 
behavior
-       if strings.Contains(msg, "connection reset by peer") ||
-               strings.Contains(msg, "failed to receive the preface from 
client") ||
-               strings.Contains(msg, "connection error") {
-               return
-       }
-       msg = cleanMessage(msg)
-       l.logger.Printf("ERROR: %s", msg)
-}
-
-func (l *grpcLogger) Fatal(args ...interface{}) {
-       l.logger.Fatal(args...)
-}
-
-func (l *grpcLogger) Fatalln(args ...interface{}) {
-       l.logger.Fatal(args...)
-}
-
-func (l *grpcLogger) Fatalf(format string, args ...interface{}) {
-       l.logger.Fatalf(format, args...)
-}
-
-func (l *grpcLogger) V(level int) bool {
-       return level <= 0
-}
-
-// waitForBootstrapFile waits for the grpc-bootstrap.json file to exist
-// This is necessary because the dubbo-proxy sidecar needs time to generate 
the file
-func waitForBootstrapFile(bootstrapPath string, maxWait time.Duration) error {
-       log.Printf("Waiting for bootstrap file to exist: %s (max wait: %v)", 
bootstrapPath, maxWait)
-
-       ctx, cancel := context.WithTimeout(context.Background(), maxWait)
-       defer cancel()
-
-       ticker := time.NewTicker(500 * time.Millisecond)
-       defer ticker.Stop()
-
-       startTime := time.Now()
-       for {
-               // Check if file exists and is not empty
-               if info, err := os.Stat(bootstrapPath); err == nil && 
info.Size() > 0 {
-                       log.Printf("Bootstrap file found after %v: %s", 
time.Since(startTime), bootstrapPath)
-                       return nil
-               }
-
-               // Check for timeout
-               select {
-               case <-ctx.Done():
-                       return fmt.Errorf("timeout waiting for bootstrap file: 
%s (waited %v)", bootstrapPath, time.Since(startTime))
-               case <-ticker.C:
-                       // Continue waiting
-               }
-       }
-}
-
-func firstNonEmpty(values ...string) string {
-       for _, v := range values {
-               if strings.TrimSpace(v) != "" {
-                       return v
-               }
-       }
-       return ""
-}
-
-func main() {
-       flag.Parse()
-
-       // Set custom gRPC logger to filter out xDS informational logs
-       // The "ERROR: [xds] Listener entering mode: SERVING" is actually an 
informational log
-       grpclog.SetLoggerV2(&grpcLogger{
-               logger: log.New(os.Stderr, "", log.LstdFlags),
-       })
-
-       hostname, _ := os.Hostname()
-       if hostname == "" {
-               hostname = "unknown"
-       }
-
-       namespace := firstNonEmpty(os.Getenv("SERVICE_NAMESPACE"), 
os.Getenv("POD_NAMESPACE"), "default")
-       serviceVersion := firstNonEmpty(
-               os.Getenv("SERVICE_VERSION"),
-               os.Getenv("POD_VERSION"),
-               os.Getenv("VERSION"),
-       )
-       if serviceVersion == "" {
-               serviceVersion = "unknown"
-       }
-       cluster := os.Getenv("SERVICE_CLUSTER")
-       instanceIP := os.Getenv("INSTANCE_IP")
-       servicePort := *port
-       if sp := os.Getenv("SERVICE_PORT"); sp != "" {
-               if parsed, err := strconv.Atoi(sp); err == nil {
-                       servicePort = parsed
-               }
-       }
-
-       // Get bootstrap file path from environment variable or use default
-       bootstrapPath := os.Getenv("GRPC_XDS_BOOTSTRAP")
-       if bootstrapPath == "" {
-               bootstrapPath = "/etc/dubbo/proxy/grpc-bootstrap.json"
-               log.Printf("GRPC_XDS_BOOTSTRAP not set, using default: %s", 
bootstrapPath)
-       }
-
-       // Wait for bootstrap file to exist before creating xDS server
-       // The dubbo-proxy sidecar needs time to generate this file
-       if err := waitForBootstrapFile(bootstrapPath, 60*time.Second); err != 
nil {
-               log.Fatalf("Failed to wait for bootstrap file: %v", err)
-       }
-
-       // Create xDS-enabled gRPC server
-       // For proxyless gRPC, we use xds.NewGRPCServer() instead of 
grpc.NewServer()
-       // NOTE: FallbackCreds is REQUIRED by gRPC xDS library for initial 
connection
-       // before xDS configuration is available. However, once xDS configures 
TLS,
-       // the server will use TLS and will NOT fallback to plaintext if TLS 
fails.
-       // FallbackCreds is only used when xDS has not yet provided TLS 
configuration.
-       creds, err := xdscreds.NewServerCredentials(xdscreds.ServerOptions{
-               FallbackCreds: insecure.NewCredentials(),
-       })
-       if err != nil {
-               log.Fatalf("Failed to create xDS server credentials: %v", err)
-       }
-
-       server, err := xds.NewGRPCServer(grpc.Creds(creds))
-       if err != nil {
-               log.Fatalf("Failed to create xDS gRPC server: %v", err)
-       }
-
-       es := &echoServer{
-               hostname:       hostname,
-               serviceVersion: serviceVersion,
-               namespace:      namespace,
-               instanceIP:     instanceIP,
-               cluster:        cluster,
-               servicePort:    servicePort,
-       }
-       pb.RegisterEchoServiceServer(server, es)
-       pb.RegisterEchoTestServiceServer(server, es)
-       // Enable reflection API for grpcurl to discover services
-       reflection.Register(server)
-
-       lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port))
-       if err != nil {
-               log.Fatalf("Failed to listen: %v", err)
-       }
-
-       log.Printf("Starting gRPC proxyless server on port %d (hostname: %s)", 
*port, hostname)
-
-       go func() {
-               sigChan := make(chan os.Signal, 1)
-               signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
-               <-sigChan
-               log.Println("Shutting down server...")
-               server.GracefulStop()
-       }()
-
-       // Serve the gRPC server
-       // Note: server.Serve returns when the listener is closed, which is 
normal during shutdown
-       // Connection reset errors are handled by the gRPC library and logged 
separately
-       if err := server.Serve(lis); err != nil {
-               // Only log as fatal if it's not a normal shutdown (listener 
closed)
-               if !strings.Contains(err.Error(), "use of closed network 
connection") {
-                       log.Fatalf("Failed to serve: %v", err)
-               }
-               log.Printf("Server stopped: %v", err)
-       }
-}
diff --git a/tests/loadtest/go.mod b/tests/loadtest/go.mod
index a57dda58..281b7481 100644
--- a/tests/loadtest/go.mod
+++ b/tests/loadtest/go.mod
@@ -16,32 +16,3 @@
 module github.com/apache/dubbo-kubernetes/test/loadtest
 
 go 1.24.0
-
-require (
-       github.com/fatih/color v1.16.0
-       google.golang.org/grpc v1.76.0
-       google.golang.org/protobuf v1.36.10
-)
-
-require (
-       cel.dev/expr v0.24.0 // indirect
-       cloud.google.com/go/compute/metadata v0.7.0 // indirect
-       github.com/cespare/xxhash/v2 v2.3.0 // indirect
-       github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
-       github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
-       github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
-       github.com/go-jose/go-jose/v4 v4.1.2 // indirect
-       github.com/mattn/go-colorable v0.1.13 // indirect
-       github.com/mattn/go-isatty v0.0.20 // indirect
-       github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 
// indirect
-       github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
-       github.com/zeebo/errs v1.4.0 // indirect
-       golang.org/x/crypto v0.43.0 // indirect
-       golang.org/x/net v0.46.0 // indirect
-       golang.org/x/oauth2 v0.30.0 // indirect
-       golang.org/x/sync v0.17.0 // indirect
-       golang.org/x/sys v0.37.0 // indirect
-       golang.org/x/text v0.30.0 // indirect
-       google.golang.org/genproto/googleapis/api 
v0.0.0-20250804133106-a7a43d27e69b // indirect
-       google.golang.org/genproto/googleapis/rpc 
v0.0.0-20251103181224-f26f9409b101 // indirect
-)
diff --git a/tests/loadtest/go.sum b/tests/loadtest/go.sum
index 47c916ae..e69de29b 100644
--- a/tests/loadtest/go.sum
+++ b/tests/loadtest/go.sum
@@ -1,85 +0,0 @@
-cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
-cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
-cloud.google.com/go/compute/metadata v0.7.0 
h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
-cloud.google.com/go/compute/metadata v0.7.0/go.mod 
h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
-github.com/cespare/xxhash/v2 v2.3.0 
h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
-github.com/cespare/xxhash/v2 v2.3.0/go.mod 
h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 
h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
-github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod 
h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
-github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/envoyproxy/go-control-plane v0.13.4 
h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
-github.com/envoyproxy/go-control-plane v0.13.4/go.mod 
h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4 
h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
-github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod 
h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
-github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 
h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
-github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod 
h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
-github.com/envoyproxy/protoc-gen-validate v1.2.1 
h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
-github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod 
h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
-github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
-github.com/fatih/color v1.16.0/go.mod 
h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
-github.com/go-jose/go-jose/v4 v4.1.2 
h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
-github.com/go-jose/go-jose/v4 v4.1.2/go.mod 
h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod 
h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
-github.com/go-logr/stdr v1.2.2/go.mod 
h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/golang/protobuf v1.5.4 
h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod 
h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod 
h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod 
h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/mattn/go-colorable v0.1.13 
h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod 
h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod 
h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.20 
h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod 
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 
h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
-github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod 
h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
-github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/spiffe/go-spiffe/v2 v2.5.0 
h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
-github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod 
h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
-github.com/stretchr/testify v1.10.0 
h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
-github.com/zeebo/errs v1.4.0/go.mod 
h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
-go.opentelemetry.io/auto/sdk v1.1.0 
h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod 
h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/otel v1.37.0 
h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod 
h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 
h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod 
h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/sdk v1.37.0 
h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
-go.opentelemetry.io/otel/sdk v1.37.0/go.mod 
h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
-go.opentelemetry.io/otel/sdk/metric v1.37.0 
h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
-go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod 
h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
-go.opentelemetry.io/otel/trace v1.37.0 
h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod 
h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod 
h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
-golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
-golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
-golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
-golang.org/x/oauth2 v0.30.0/go.mod 
h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod 
h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod 
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod 
h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
-gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
-gonum.org/v1/gonum v0.16.0/go.mod 
h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b 
h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc=
-google.golang.org/genproto/googleapis/api 
v0.0.0-20250804133106-a7a43d27e69b/go.mod 
h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 
h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
-google.golang.org/genproto/googleapis/rpc 
v0.0.0-20251103181224-f26f9409b101/go.mod 
h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
-google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
-google.golang.org/grpc v1.76.0/go.mod 
h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
-google.golang.org/protobuf v1.36.10 
h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
-google.golang.org/protobuf v1.36.10/go.mod 
h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tools/scripts/run.sh b/tools/scripts/run.sh
new file mode 100755
index 00000000..e6c6d9ab
--- /dev/null
+++ b/tools/scripts/run.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+set -e
+
+WD=$(dirname "$0")
+WD=$(cd "$WD"; pwd)
+
+export FOR_BUILD_CONTAINER=1
+# shellcheck disable=SC1090,SC1091
+source "${WD}/setup_env.sh"
+
+
+MOUNT_SOURCE="${MOUNT_SOURCE:-${PWD}}"
+MOUNT_DEST="${MOUNT_DEST:-/work}"
+
+read -ra DOCKER_RUN_OPTIONS <<< "${DOCKER_RUN_OPTIONS:-}"
+
+[[ -t 0 ]] && DOCKER_RUN_OPTIONS+=("-it")
+[[ ${UID} -ne 0 ]] && DOCKER_RUN_OPTIONS+=(-u "${UID}:${DOCKER_GID}")
+
+# $CONTAINER_OPTIONS becomes an empty arg when quoted, so SC2086 is disabled 
for the
+# following command only
+# shellcheck disable=SC2086
+"${CONTAINER_CLI}" run \
+    --rm \
+    "${DOCKER_RUN_OPTIONS[@]}" \
+    --init \
+    --sig-proxy=true \
+    --cap-add=SYS_ADMIN \
+    ${DOCKER_SOCKET_MOUNT:--v /var/run/docker.sock:/var/run/docker.sock} \
+    -e DOCKER_HOST=${DOCKER_SOCKET_HOST:-unix:///var/run/docker.sock} \
+    $CONTAINER_OPTIONS \
+    --env-file <(env | grep -v "${ENV_BLOCKLIST:-^$}") \
+    -e IN_BUILD_CONTAINER=1 \
+    -e TZ="${TIMEZONE:-$TZ}" \
+    --mount "type=bind,source=${MOUNT_SOURCE},destination=/work" \
+    --mount "type=volume,source=go,destination=/go" \
+    --mount "type=volume,source=gocache,destination=/gocache" \
+    --mount "type=volume,source=cache,destination=/home/.cache" \
+    --mount "type=volume,source=crates,destination=/home/.cargo/registry" \
+    --mount "type=volume,source=git-crates,destination=/home/.cargo/git" \
+    ${CONDITIONAL_HOST_MOUNTS} \
+    -w "${MOUNT_DEST}" "${IMG}" "$@"
diff --git a/tools/scripts/setup_env.sh b/tools/scripts/setup_env.sh
new file mode 100755
index 00000000..370b4c29
--- /dev/null
+++ b/tools/scripts/setup_env.sh
@@ -0,0 +1,145 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC2034
+
+set -e
+
+# 
https://stackoverflow.com/questions/59895/how-can-i-get-the-source-directory-of-a-bash-script-from-within-the-script-itsel
+# Note: the normal way we use in other scripts in Istio do not work when 
`source`d, which is why we use this approach
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+REPO_ROOT="$(dirname "$(dirname "${SCRIPT_DIR}")")"
+
+LOCAL_ARCH=$(uname -m)
+
+# Pass environment set target architecture to build system
+if [[ ${TARGET_ARCH} ]]; then
+    # Target explicitly set
+    :
+elif [[ ${LOCAL_ARCH} == x86_64 ]]; then
+    TARGET_ARCH=amd64
+elif [[ ${LOCAL_ARCH} == armv8* ]]; then
+    TARGET_ARCH=arm64
+elif [[ ${LOCAL_ARCH} == arm64* ]]; then
+    TARGET_ARCH=arm64
+elif [[ ${LOCAL_ARCH} == aarch64* ]]; then
+    TARGET_ARCH=arm64
+else
+    echo "This system's architecture, ${LOCAL_ARCH}, isn't supported"
+    exit 1
+fi
+
+LOCAL_OS=$(uname)
+
+# Pass environment set target operating-system to build system
+if [[ ${TARGET_OS} ]]; then
+    # Target explicitly set
+    :
+elif [[ $LOCAL_OS == Linux ]]; then
+    TARGET_OS=linux
+    readlink_flags="-f"
+elif [[ $LOCAL_OS == Darwin ]]; then
+    TARGET_OS=darwin
+    readlink_flags=""
+else
+    echo "This system's OS, $LOCAL_OS, isn't supported"
+    exit 1
+fi
+
+TIMEZONE=$(readlink "$readlink_flags" /etc/localtime | sed -e 
's/^.*zoneinfo\///')
+
+ENV_BLOCKLIST="${ENV_BLOCKLIST:-^_\|^PATH=\|^GOPATH=\|^GOROOT=\|^SHELL=\|^EDITOR=\|^TMUX=\|^USER=\|^HOME=\|^PWD=\|^TERM=\|^RUBY_\|^GEM_\|^rvm_\|^SSH=\|^TMPDIR=\|^CC=\|^CXX=\|^MAKEFILE_LIST=}"
+
+# Build image to use
+TOOLS_REGISTRY_PROVIDER=${TOOLS_REGISTRY_PROVIDER:-docker.io}
+PROJECT_ID=${PROJECT_ID:-mfordjody}
+if [[ "${IMAGE_VERSION:-}" == "" ]]; then
+  IMAGE_VERSION=master
+fi
+if [[ "${IMAGE_NAME:-}" == "" ]]; then
+  IMAGE_NAME=build-tools
+fi
+
+#TOOLS_REGISTRY_PROVIDER=${TOOLS_REGISTRY_PROVIDER:-gcr.io}
+#PROJECT_ID=${PROJECT_ID:-istio-testing}
+#if [[ "${IMAGE_VERSION:-}" == "" ]]; then
+#  IMAGE_VERSION=master-dd350f492cf194be812d6f79d13e450f10b62e94
+#fi
+#if [[ "${IMAGE_NAME:-}" == "" ]]; then
+#  IMAGE_NAME=build-tools
+#fi
+
+CONTAINER_CLI="${CONTAINER_CLI:-docker}"
+
+# Try to use the latest cached image we have. Use at your own risk, may have 
incompatibly-old versions
+if [[ "${LATEST_CACHED_IMAGE:-}" != "" ]]; then
+  prefix="$(<<<"$IMAGE_VERSION" cut -d- -f1)"
+  query="${TOOLS_REGISTRY_PROVIDER}/${PROJECT_ID}/${IMAGE_NAME}:${prefix}-*"
+  latest="$("${CONTAINER_CLI}" images --filter=reference="${query}" --format 
"{{.CreatedAt|json}}~{{.Repository}}:{{.Tag}}~{{.CreatedSince}}" | sort -n -r | 
head -n1)"
+  IMG="$(<<<"$latest" cut -d~ -f2)"
+  if [[ "${IMG}" == "" ]]; then
+    echo "Attempted to use LATEST_CACHED_IMAGE, but found no images matching 
${query}" >&2
+    exit 1
+  fi
+  echo "Using cached image $IMG, created $(<<<"$latest" cut -d~ -f3)" >&2
+fi
+
+IMG="${IMG:-${TOOLS_REGISTRY_PROVIDER}/${PROJECT_ID}/${IMAGE_NAME}:${IMAGE_VERSION}}"
+
+TARGET_OUT="${TARGET_OUT:-$(pwd)/out/${TARGET_OS}_${TARGET_ARCH}}"
+TARGET_OUT_LINUX="${TARGET_OUT_LINUX:-$(pwd)/out/linux_${TARGET_ARCH}}"
+
+CONTAINER_TARGET_OUT="${CONTAINER_TARGET_OUT:-/work/out/${TARGET_OS}_${TARGET_ARCH}}"
+CONTAINER_TARGET_OUT_LINUX="${CONTAINER_TARGET_OUT_LINUX:-/work/out/linux_${TARGET_ARCH}}"
+
+BUILD_WITH_CONTAINER=0
+
+# LOCAL_OUT should point to architecture where we are currently running versus 
the desired.
+# This is used when we need to run a build artifact during tests or later as 
part of another
+# target.
+if [[ "${FOR_BUILD_CONTAINER:-0}" -eq "1" ]]; then
+    # Override variables with container specific
+    TARGET_OUT=${CONTAINER_TARGET_OUT}
+    TARGET_OUT_LINUX=${CONTAINER_TARGET_OUT_LINUX}
+    REPO_ROOT=/work
+    LOCAL_OUT="${TARGET_OUT_LINUX}"
+else
+    LOCAL_OUT="${TARGET_OUT}"
+fi
+
+go_os_arch=${LOCAL_OUT##*/}
+# Golang OS/Arch format
+LOCAL_GO_OS=${go_os_arch%_*}
+LOCAL_GO_ARCH=${go_os_arch##*_}
+
+VARS=(
+      CONTAINER_TARGET_OUT
+      CONTAINER_TARGET_OUT_LINUX
+      TARGET_OUT
+      TARGET_OUT_LINUX
+      LOCAL_GO_OS
+      LOCAL_GO_ARCH
+      LOCAL_OUT
+      LOCAL_OS
+      TARGET_OS
+      LOCAL_ARCH
+      TARGET_ARCH
+      TIMEZONE
+      CONTAINER_CLI
+      IMG
+      IMAGE_NAME
+      IMAGE_VERSION
+      REPO_ROOT
+      BUILD_WITH_CONTAINER
+)
+
+# For non container build, we need to write env to file
+if [[ "${1}" == "envfile" ]]; then
+    # ! does a variable-variable https://stackoverflow.com/a/10757531/374797
+    for var in "${VARS[@]}"; do
+        echo "${var}"="${!var}"
+    done
+else
+    for var in "${VARS[@]}"; do
+        # shellcheck disable=SC2163
+        export "${var}"
+    done
+fi


Reply via email to