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

mfordjody 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 a25d2362 Optimize files and standardize engineering practices (#959)
a25d2362 is described below

commit a25d2362b0b355af931dec5939773fc4265c2adf
Author: mfordjody <[email protected]>
AuthorDate: Sat Jul 18 14:37:42 2026 +0800

    Optimize files and standardize engineering practices (#959)
---
 .github/workflows/ci.yml                           |  23 ++++
 Makefile                                           | 133 +-----------------
 cni/main.go                                        |  32 ++---
 cni/pkg/{cni => nodeagent}/config.go               |   2 +-
 cni/pkg/{cni => nodeagent}/config_test.go          |   2 +-
 cni/pkg/{cni => nodeagent}/install.go              |   2 +-
 cni/pkg/{cni => nodeagent}/install_test.go         |   2 +-
 cni/pkg/{cni => nodeagent}/iptables.go             |   2 +-
 cni/pkg/{cni => nodeagent}/iptables_test.go        |   2 +-
 cni/pkg/{cni => nodeagent}/k8s.go                  |   2 +-
 cni/pkg/{cni => nodeagent}/plugin.go               |   2 +-
 cni/pkg/{cni => nodeagent}/plugin_test.go          |   2 +-
 cni/pkg/{cni => nodeagent}/state.go                |   2 +-
 {cli => dubboctl}/cmd/admin.go                     |   2 +-
 {cli => dubboctl}/cmd/admin_test.go                |   0
 {cli => dubboctl}/cmd/analyze.go                   |   2 +-
 {cli => dubboctl}/cmd/create.go                    |   6 +-
 {cli => dubboctl}/cmd/dashboard.go                 |   2 +-
 {cli => dubboctl}/cmd/dashboard_test.go            |   0
 {cli => dubboctl}/cmd/get.go                       |   2 +-
 {cli => dubboctl}/cmd/get_test.go                  |   0
 {cli => dubboctl}/cmd/gui.go                       |   0
 {cli => dubboctl}/cmd/gui_test.go                  |   0
 {cli => dubboctl}/cmd/image.go                     |  12 +-
 {cli => dubboctl}/cmd/multicluster.go              |   0
 {cli => dubboctl}/cmd/multicluster_test.go         |   0
 {cli => dubboctl}/cmd/portforward.go               |   0
 {cli => dubboctl}/cmd/proxystatus.go               |   2 +-
 {cli => dubboctl}/cmd/repo.go                      |   4 +-
 {cli => dubboctl}/cmd/root.go                      |  20 +--
 {cli => dubboctl}/main.go                          |   2 +-
 {cli => dubboctl}/pkg/cli/context.go               |   0
 {cli => dubboctl}/pkg/cli/options.go               |   0
 {cli => dubboctl}/pkg/hub/builder/builder.go       |   2 +-
 .../pkg/hub/builder/dockerfile/build.go            |   4 +-
 {cli => dubboctl}/pkg/hub/builder/pack/build.go    |   6 +-
 {cli => dubboctl}/pkg/hub/client.go                |   2 +-
 .../pkg/hub/credentials/credentials.go             |   2 +-
 .../pkg/hub/credentials/prompt/prompt.go           |   4 +-
 {cli => dubboctl}/pkg/hub/deployer/deploy.tpl      |   0
 {cli => dubboctl}/pkg/hub/deployer/deployer.go     |   6 +-
 {cli => dubboctl}/pkg/hub/pusher/pusher.go         |   4 +-
 {cli => dubboctl}/pkg/hub/pusher/transport.go      |   0
 {cli => dubboctl}/pkg/hub/ssh/agent.go             |   0
 {cli => dubboctl}/pkg/hub/ssh/agent_windows.go     |   0
 {cli => dubboctl}/pkg/hub/ssh/dialer.go            |   0
 {cli => dubboctl}/pkg/hub/ssh/terminal.go          |   0
 {cli => dubboctl}/pkg/sdk/client.go                |   4 +-
 {cli => dubboctl}/pkg/sdk/dubbo/config.go          |   0
 {cli => dubboctl}/pkg/sdk/dubbo/dockerfile.go      |   0
 {cli => dubboctl}/pkg/sdk/repositories.go          |   0
 {cli => dubboctl}/pkg/sdk/repository.go            |   2 +-
 {cli => dubboctl}/pkg/sdk/template.go              |   4 +-
 {cli => dubboctl}/pkg/sdk/templates.go             |   4 +-
 {cli => dubboctl}/pkg/sdk/templates_embedded.go    |   4 +-
 .../pkg/sdk/tpl/default/manifest.yaml              |   0
 {cli => dubboctl}/pkg/sdk/tpl/filesystem.go        |   0
 .../pkg/sdk/tpl/zz_filesystem_generated.go         |   0
 {cli => dubboctl}/pkg/util/err.go                  |   0
 {cli => dubboctl}/pkg/util/file.go                 |   0
 {cli => dubboctl}/pkg/util/fs.go                   |   0
 {cli => dubboctl}/pkg/util/path.go                 |   0
 {cli => dubboctl}/pkg/util/sortedset.go            |   0
 {cli => dubboctl}/pkg/validate/validate.go         |   2 +-
 {cli => dubboctl}/pkg/version/version.go           |   0
 manifests/charts/README.md                         |  18 ++-
 manifests/charts/dubbod/templates/deployment.yaml  |  11 ++
 .../dubbod/templates/poddisruptionbudget.yaml      |  23 +++-
 .../templates/validatingwebhookconfiguration.yaml  |   9 +-
 manifests/charts/dubbod/values.yaml                |   4 +
 operator/cmd/cluster/install.go                    |   2 +-
 operator/cmd/cluster/manifest.go                   |   2 +-
 operator/cmd/cluster/uninstall.go                  |   2 +-
 operator/cmd/cluster/upgrade.go                    |   2 +-
 tests/e2e/run.sh                                   | 149 +++++++++++++++++++++
 .../e2e/testdata/serviceentry.yaml                 |  19 ++-
 tools/make/build.mk                                |  43 ++++++
 tools/make/common.mk                               |  49 +++++++
 tools/make/lint.mk                                 |  73 ++++++++++
 .../default/manifest.yaml => tools/make/test.mk    |  19 ++-
 80 files changed, 504 insertions(+), 233 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f84da827..cb30f9d3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -170,3 +170,26 @@ jobs:
 
       - name: Lint and render charts
         run: make lint-helm
+
+  e2e:
+    name: E2E Smoke (kind)
+    runs-on: ubuntu-latest
+    timeout-minutes: 40
+    if: github.repository == 'apache/dubbo-kubernetes'
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Install Helm
+        uses: azure/setup-helm@v4
+        with:
+          version: 'v3.16.4'
+
+      - name: Install kind
+        uses: helm/kind-action@v1
+        with:
+          install_only: true
+          version: 'v0.30.0'
+
+      - name: Run e2e smoke test
+        run: make test-e2e
diff --git a/Makefile b/Makefile
index 14af9508..74633130 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@
 # (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
+#      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,
@@ -13,129 +13,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-GOOS ?= $(shell go env GOOS)
-GOARCH ?= $(shell go env GOARCH)
-GIT_VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || 
echo unknown)
+# Thin entrypoint; the real targets live in tools/make/*.mk.
+# Run `make help` for the full target list.
 
-GOLANGCI_LINT_VERSION ?= v2.6.2
-GOTESTFLAGS ?= -race
-
-.PHONY: default
-default: build
-
-# ------------------------------------------------------------------------
-# Build
-# ------------------------------------------------------------------------
-
-.PHONY: build
-build: build-dubboctl build-dubbod build-dubbo-cni
-
-.PHONY: build-dubboctl
-build-dubboctl:
-       CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build \
-       -ldflags "-X 
github.com/apache/dubbo-kubernetes/pkg/version.gitTag=$(GIT_VERSION)" \
-    -o bin/dubboctl cli/main.go
-
-.PHONY: build-dubbod
-build-dubbod:
-       CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build \
-    -o bin/dubbod dubbod/discovery/cmd/main.go
-
-.PHONY: build-dubbo-cni
-build-dubbo-cni:
-       CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build \
-    -o bin/dubbo-cni cni/main.go
-
-.PHONY: clone-sample
-clone-sample:
-       mkdir -p bin
-       cp -r samples bin/samples
-
-# ------------------------------------------------------------------------
-# Test
-# ------------------------------------------------------------------------
-
-.PHONY: test
-test:
-       go test $(GOTESTFLAGS) ./...
-
-.PHONY: test-coverage
-test-coverage:
-       go test $(GOTESTFLAGS) -coverprofile=coverage.txt -covermode=atomic 
./...
-
-# ------------------------------------------------------------------------
-# Lint / format
-# ------------------------------------------------------------------------
-
-.PHONY: lint
-lint: lint-go lint-shell
-
-.PHONY: lint-go
-lint-go:
-       @if ! command -v golangci-lint >/dev/null 2>&1; then \
-               echo "golangci-lint not found; install $(GOLANGCI_LINT_VERSION) 
from https://golangci-lint.run/welcome/install/";; \
-               exit 1; \
-       fi
-       golangci-lint run ./...
-
-.PHONY: lint-shell
-lint-shell:
-       @if command -v shellcheck >/dev/null 2>&1; then \
-               find . -name '*.sh' -not -path './vendor/*' -not -path 
'./bin/*' -print0 | xargs -0 -r shellcheck; \
-       else \
-               echo "shellcheck not found, skipping shell lint"; \
-       fi
-
-.PHONY: fmt
-fmt:
-       gofmt -s -w .
-
-.PHONY: check-fmt
-check-fmt:
-       @out="$$(gofmt -s -l .)"; \
-       if [ -n "$$out" ]; then \
-               echo "The following files need 'gofmt -s':"; \
-               echo "$$out"; \
-               exit 1; \
-       fi
-
-# ------------------------------------------------------------------------
-# Hygiene gates (CI runs these; they must leave the tree clean)
-# ------------------------------------------------------------------------
-
-.PHONY: tidy
-tidy:
-       go mod tidy
-
-.PHONY: check-clean-repo
-check-clean-repo:
-       @if [ -n "$$(git status --porcelain)" ]; then \
-               echo "The working tree is dirty after running 
generators/tidy:"; \
-               git status --porcelain; \
-               git diff; \
-               exit 1; \
-       fi
-
-.PHONY: check-tidy
-check-tidy: tidy check-clean-repo
-
-# ------------------------------------------------------------------------
-# Helm
-# ------------------------------------------------------------------------
-
-CHARTS := manifests/charts/base manifests/charts/dubbod
-
-.PHONY: lint-helm
-lint-helm:
-       @for chart in $(CHARTS); do \
-               helm lint $$chart || exit 1; \
-               helm template test-release $$chart >/dev/null || exit 1; \
-       done
-
-# ------------------------------------------------------------------------
-# Misc
-# ------------------------------------------------------------------------
-
-.PHONY: clean
-clean:
-       rm -rf bin coverage.txt
+include tools/make/common.mk
+include tools/make/build.mk
+include tools/make/test.mk
+include tools/make/lint.mk
diff --git a/cni/main.go b/cni/main.go
index 6beaafaa..7f79188f 100644
--- a/cni/main.go
+++ b/cni/main.go
@@ -25,7 +25,7 @@ import (
        "syscall"
        "time"
 
-       "github.com/apache/dubbo-kubernetes/cni/pkg/cni"
+       "github.com/apache/dubbo-kubernetes/cni/pkg/nodeagent"
 )
 
 func main() {
@@ -49,13 +49,13 @@ func main() {
                exitErr(fmt.Errorf("read stdin: %w", err))
        }
 
-       conf, err := cni.ParseNetConf(stdin)
+       conf, err := nodeagent.ParseNetConf(stdin)
        if err != nil {
                exitErr(err)
        }
-       env := cni.EnvFromOS()
+       env := nodeagent.EnvFromOS()
        if env.Command == "VERSION" {
-               out, err := cni.Plugin{}.Run(context.Background(), env, conf)
+               out, err := nodeagent.Plugin{}.Run(context.Background(), env, 
conf)
                if err != nil {
                        exitErr(err)
                }
@@ -65,12 +65,12 @@ func main() {
                return
        }
 
-       plugin := cni.Plugin{
-               RuleManager: cni.NewIPTablesRuleManager(conf),
-               StateStore:  cni.NewFileStateStore(conf.StateDirectory()),
+       plugin := nodeagent.Plugin{
+               RuleManager: nodeagent.NewIPTablesRuleManager(conf),
+               StateStore:  nodeagent.NewFileStateStore(conf.StateDirectory()),
        }
-       if (env.Command == "ADD" || env.Command == "CHECK") && 
cni.EnvHasKubernetesPod(env) {
-               provider, err := 
cni.NewK8sPodInfoProvider(conf.KubeConfigPath())
+       if (env.Command == "ADD" || env.Command == "CHECK") && 
nodeagent.EnvHasKubernetesPod(env) {
+               provider, err := 
nodeagent.NewK8sPodInfoProvider(conf.KubeConfigPath())
                if err == nil {
                        plugin.PodInfoProvider = provider
                }
@@ -87,33 +87,33 @@ func main() {
 }
 
 func runInstall(args []string) error {
-       opts := cni.DefaultInstallerOptions()
+       opts := nodeagent.DefaultInstallerOptions()
        var watch bool
        var interval time.Duration
        flags := installerFlagSet("install", &opts)
        flags.BoolVar(&watch, "watch", false, "keep installer running and 
refresh service account credentials")
-       flags.DurationVar(&interval, "refresh-interval", 
cni.DefaultInstallerInterval(), "credential refresh interval")
+       flags.DurationVar(&interval, "refresh-interval", 
nodeagent.DefaultInstallerInterval(), "credential refresh interval")
        if err := flags.Parse(args); err != nil {
                return err
        }
        ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, 
syscall.SIGTERM)
        defer stop()
        if watch {
-               return cni.InstallLoop(ctx, opts, interval)
+               return nodeagent.InstallLoop(ctx, opts, interval)
        }
-       return cni.Install(ctx, opts)
+       return nodeagent.Install(ctx, opts)
 }
 
 func runUninstall(args []string) error {
-       opts := cni.DefaultInstallerOptions()
+       opts := nodeagent.DefaultInstallerOptions()
        flags := installerFlagSet("uninstall", &opts)
        if err := flags.Parse(args); err != nil {
                return err
        }
-       return cni.Uninstall(opts)
+       return nodeagent.Uninstall(opts)
 }
 
-func installerFlagSet(name string, opts *cni.InstallerOptions) *flag.FlagSet {
+func installerFlagSet(name string, opts *nodeagent.InstallerOptions) 
*flag.FlagSet {
        flags := flag.NewFlagSet(name, flag.ContinueOnError)
        flags.StringVar(&opts.BinDir, "bin-dir", opts.BinDir, "host CNI binary 
directory")
        flags.StringVar(&opts.ConfDir, "conf-dir", opts.ConfDir, "host CNI 
config directory")
diff --git a/cni/pkg/cni/config.go b/cni/pkg/nodeagent/config.go
similarity index 99%
rename from cni/pkg/cni/config.go
rename to cni/pkg/nodeagent/config.go
index 669d0ac8..35e7f415 100644
--- a/cni/pkg/cni/config.go
+++ b/cni/pkg/nodeagent/config.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "encoding/json"
diff --git a/cni/pkg/cni/config_test.go b/cni/pkg/nodeagent/config_test.go
similarity index 99%
rename from cni/pkg/cni/config_test.go
rename to cni/pkg/nodeagent/config_test.go
index d0773c3d..b2287cd2 100644
--- a/cni/pkg/cni/config_test.go
+++ b/cni/pkg/nodeagent/config_test.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "encoding/json"
diff --git a/cni/pkg/cni/install.go b/cni/pkg/nodeagent/install.go
similarity index 99%
rename from cni/pkg/cni/install.go
rename to cni/pkg/nodeagent/install.go
index 2d9ef116..9b1fd28e 100644
--- a/cni/pkg/cni/install.go
+++ b/cni/pkg/nodeagent/install.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "context"
diff --git a/cni/pkg/cni/install_test.go b/cni/pkg/nodeagent/install_test.go
similarity index 99%
rename from cni/pkg/cni/install_test.go
rename to cni/pkg/nodeagent/install_test.go
index 07a1f93e..cb46827f 100644
--- a/cni/pkg/cni/install_test.go
+++ b/cni/pkg/nodeagent/install_test.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "context"
diff --git a/cni/pkg/cni/iptables.go b/cni/pkg/nodeagent/iptables.go
similarity index 99%
rename from cni/pkg/cni/iptables.go
rename to cni/pkg/nodeagent/iptables.go
index bad4eec5..0645eb0c 100644
--- a/cni/pkg/cni/iptables.go
+++ b/cni/pkg/nodeagent/iptables.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "bytes"
diff --git a/cni/pkg/cni/iptables_test.go b/cni/pkg/nodeagent/iptables_test.go
similarity index 99%
rename from cni/pkg/cni/iptables_test.go
rename to cni/pkg/nodeagent/iptables_test.go
index ea8af9f6..0bae03b2 100644
--- a/cni/pkg/cni/iptables_test.go
+++ b/cni/pkg/nodeagent/iptables_test.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "context"
diff --git a/cni/pkg/cni/k8s.go b/cni/pkg/nodeagent/k8s.go
similarity index 99%
rename from cni/pkg/cni/k8s.go
rename to cni/pkg/nodeagent/k8s.go
index dfcd3542..b23ed658 100644
--- a/cni/pkg/cni/k8s.go
+++ b/cni/pkg/nodeagent/k8s.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "context"
diff --git a/cni/pkg/cni/plugin.go b/cni/pkg/nodeagent/plugin.go
similarity index 99%
rename from cni/pkg/cni/plugin.go
rename to cni/pkg/nodeagent/plugin.go
index f193695a..42f6e706 100644
--- a/cni/pkg/cni/plugin.go
+++ b/cni/pkg/nodeagent/plugin.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "context"
diff --git a/cni/pkg/cni/plugin_test.go b/cni/pkg/nodeagent/plugin_test.go
similarity index 99%
rename from cni/pkg/cni/plugin_test.go
rename to cni/pkg/nodeagent/plugin_test.go
index 9f733100..ff55e5d7 100644
--- a/cni/pkg/cni/plugin_test.go
+++ b/cni/pkg/nodeagent/plugin_test.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "context"
diff --git a/cni/pkg/cni/state.go b/cni/pkg/nodeagent/state.go
similarity index 99%
rename from cni/pkg/cni/state.go
rename to cni/pkg/nodeagent/state.go
index 169566d0..ac35612d 100644
--- a/cni/pkg/cni/state.go
+++ b/cni/pkg/nodeagent/state.go
@@ -13,7 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package cni
+package nodeagent
 
 import (
        "crypto/sha256"
diff --git a/cli/cmd/admin.go b/dubboctl/cmd/admin.go
similarity index 98%
rename from cli/cmd/admin.go
rename to dubboctl/cmd/admin.go
index 63d9d88b..04bf509f 100644
--- a/cli/cmd/admin.go
+++ b/dubboctl/cmd/admin.go
@@ -22,7 +22,7 @@ import (
        "io"
        "sort"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/spf13/cobra"
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/cli/cmd/admin_test.go b/dubboctl/cmd/admin_test.go
similarity index 100%
rename from cli/cmd/admin_test.go
rename to dubboctl/cmd/admin_test.go
diff --git a/cli/cmd/analyze.go b/dubboctl/cmd/analyze.go
similarity index 99%
rename from cli/cmd/analyze.go
rename to dubboctl/cmd/analyze.go
index 0c214bf9..dc1af1fc 100644
--- a/cli/cmd/analyze.go
+++ b/dubboctl/cmd/analyze.go
@@ -22,7 +22,7 @@ import (
        "io"
        "strings"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/pkg/kube"
        "github.com/spf13/cobra"
        corev1 "k8s.io/api/core/v1"
diff --git a/cli/cmd/create.go b/dubboctl/cmd/create.go
similarity index 97%
rename from cli/cmd/create.go
rename to dubboctl/cmd/create.go
index e995a5fd..adac4a0f 100644
--- a/cli/cmd/create.go
+++ b/dubboctl/cmd/create.go
@@ -19,9 +19,9 @@ package cmd
 import (
        "errors"
        "fmt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
        "github.com/apache/dubbo-kubernetes/operator/cmd/cluster"
        "github.com/ory/viper"
        "github.com/spf13/cobra"
diff --git a/cli/cmd/dashboard.go b/dubboctl/cmd/dashboard.go
similarity index 99%
rename from cli/cmd/dashboard.go
rename to dubboctl/cmd/dashboard.go
index 7759f748..d56e5205 100644
--- a/cli/cmd/dashboard.go
+++ b/dubboctl/cmd/dashboard.go
@@ -27,7 +27,7 @@ import (
        "path/filepath"
        "time"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/pkg/kube"
        "github.com/spf13/cobra"
        appsv1 "k8s.io/api/apps/v1"
diff --git a/cli/cmd/dashboard_test.go b/dubboctl/cmd/dashboard_test.go
similarity index 100%
rename from cli/cmd/dashboard_test.go
rename to dubboctl/cmd/dashboard_test.go
diff --git a/cli/cmd/get.go b/dubboctl/cmd/get.go
similarity index 99%
rename from cli/cmd/get.go
rename to dubboctl/cmd/get.go
index 108bc991..f2566c86 100644
--- a/cli/cmd/get.go
+++ b/dubboctl/cmd/get.go
@@ -25,7 +25,7 @@ import (
        "text/tabwriter"
        "time"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/pkg/kube/inject"
        "github.com/kdubbo/api/annotation"
        "github.com/spf13/cobra"
diff --git a/cli/cmd/get_test.go b/dubboctl/cmd/get_test.go
similarity index 100%
rename from cli/cmd/get_test.go
rename to dubboctl/cmd/get_test.go
diff --git a/cli/cmd/gui.go b/dubboctl/cmd/gui.go
similarity index 100%
rename from cli/cmd/gui.go
rename to dubboctl/cmd/gui.go
diff --git a/cli/cmd/gui_test.go b/dubboctl/cmd/gui_test.go
similarity index 100%
rename from cli/cmd/gui_test.go
rename to dubboctl/cmd/gui_test.go
diff --git a/cli/cmd/image.go b/dubboctl/cmd/image.go
similarity index 96%
rename from cli/cmd/image.go
rename to dubboctl/cmd/image.go
index 7627e381..cf995d4d 100644
--- a/cli/cmd/image.go
+++ b/dubboctl/cmd/image.go
@@ -24,12 +24,12 @@ import (
        "strings"
 
        "github.com/AlecAivazis/survey/v2"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/builder/dockerfile"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/builder/pack"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/builder/dockerfile"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/builder/pack"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
        "github.com/ory/viper"
        "github.com/spf13/cobra"
 )
diff --git a/cli/cmd/multicluster.go b/dubboctl/cmd/multicluster.go
similarity index 100%
rename from cli/cmd/multicluster.go
rename to dubboctl/cmd/multicluster.go
diff --git a/cli/cmd/multicluster_test.go b/dubboctl/cmd/multicluster_test.go
similarity index 100%
rename from cli/cmd/multicluster_test.go
rename to dubboctl/cmd/multicluster_test.go
diff --git a/cli/cmd/portforward.go b/dubboctl/cmd/portforward.go
similarity index 100%
rename from cli/cmd/portforward.go
rename to dubboctl/cmd/portforward.go
diff --git a/cli/cmd/proxystatus.go b/dubboctl/cmd/proxystatus.go
similarity index 99%
rename from cli/cmd/proxystatus.go
rename to dubboctl/cmd/proxystatus.go
index 9f34593e..20a249f7 100644
--- a/cli/cmd/proxystatus.go
+++ b/dubboctl/cmd/proxystatus.go
@@ -25,7 +25,7 @@ import (
        "strings"
        "text/tabwriter"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/pkg/kube"
        "github.com/spf13/cobra"
        corev1 "k8s.io/api/core/v1"
diff --git a/cli/cmd/repo.go b/dubboctl/cmd/repo.go
similarity index 97%
rename from cli/cmd/repo.go
rename to dubboctl/cmd/repo.go
index 000544ea..bd27aac9 100644
--- a/cli/cmd/repo.go
+++ b/dubboctl/cmd/repo.go
@@ -18,8 +18,8 @@ package cmd
 
 import (
        "fmt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
        "github.com/apache/dubbo-kubernetes/operator/cmd/cluster"
        "github.com/spf13/cobra"
 )
diff --git a/cli/cmd/root.go b/dubboctl/cmd/root.go
similarity index 88%
rename from cli/cmd/root.go
rename to dubboctl/cmd/root.go
index 8805ab64..5685ccc4 100644
--- a/cli/cmd/root.go
+++ b/dubboctl/cmd/root.go
@@ -19,16 +19,16 @@ package cmd
 import (
        "flag"
        "fmt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/builder/pack"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/credentials"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/credentials/prompt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/deployer"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/pusher"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/validate"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/version"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/builder/pack"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/credentials"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/credentials/prompt"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/deployer"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/pusher"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/validate"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/version"
        "github.com/apache/dubbo-kubernetes/operator/cmd/cluster"
        "github.com/spf13/cobra"
        "net/http"
diff --git a/cli/main.go b/dubboctl/main.go
similarity index 94%
rename from cli/main.go
rename to dubboctl/main.go
index 6e6414c2..351e0f20 100644
--- a/cli/main.go
+++ b/dubboctl/main.go
@@ -21,7 +21,7 @@ import (
 )
 
 import (
-       "github.com/apache/dubbo-kubernetes/cli/cmd"
+       "github.com/apache/dubbo-kubernetes/dubboctl/cmd"
 )
 
 func main() {
diff --git a/cli/pkg/cli/context.go b/dubboctl/pkg/cli/context.go
similarity index 100%
rename from cli/pkg/cli/context.go
rename to dubboctl/pkg/cli/context.go
diff --git a/cli/pkg/cli/options.go b/dubboctl/pkg/cli/options.go
similarity index 100%
rename from cli/pkg/cli/options.go
rename to dubboctl/pkg/cli/options.go
diff --git a/cli/pkg/hub/builder/builder.go 
b/dubboctl/pkg/hub/builder/builder.go
similarity index 96%
rename from cli/pkg/hub/builder/builder.go
rename to dubboctl/pkg/hub/builder/builder.go
index 6ce5a5d4..9bec3046 100644
--- a/cli/pkg/hub/builder/builder.go
+++ b/dubboctl/pkg/hub/builder/builder.go
@@ -18,7 +18,7 @@ package builder
 
 import (
        "fmt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
 )
 
 const (
diff --git a/cli/pkg/hub/builder/dockerfile/build.go 
b/dubboctl/pkg/hub/builder/dockerfile/build.go
similarity index 93%
rename from cli/pkg/hub/builder/dockerfile/build.go
rename to dubboctl/pkg/hub/builder/dockerfile/build.go
index 0fb4b514..44d3b8be 100644
--- a/cli/pkg/hub/builder/dockerfile/build.go
+++ b/dubboctl/pkg/hub/builder/dockerfile/build.go
@@ -20,8 +20,8 @@ import (
        "context"
        "os"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
        "github.com/containers/storage/pkg/archive"
        "github.com/moby/moby/client"
        "github.com/moby/moby/client/pkg/jsonmessage"
diff --git a/cli/pkg/hub/builder/pack/build.go 
b/dubboctl/pkg/hub/builder/pack/build.go
similarity index 95%
rename from cli/pkg/hub/builder/pack/build.go
rename to dubboctl/pkg/hub/builder/pack/build.go
index b953cd96..5044830e 100644
--- a/cli/pkg/hub/builder/pack/build.go
+++ b/dubboctl/pkg/hub/builder/pack/build.go
@@ -20,9 +20,9 @@ import (
        "bytes"
        "context"
        "fmt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/builder"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/builder"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
        pack "github.com/buildpacks/pack/pkg/client"
        "github.com/buildpacks/pack/pkg/logging"
        "github.com/buildpacks/pack/pkg/project/types"
diff --git a/cli/pkg/hub/client.go b/dubboctl/pkg/hub/client.go
similarity index 98%
rename from cli/pkg/hub/client.go
rename to dubboctl/pkg/hub/client.go
index 04fc5846..268fdc49 100644
--- a/cli/pkg/hub/client.go
+++ b/dubboctl/pkg/hub/client.go
@@ -30,7 +30,7 @@ import (
        "strconv"
        "time"
 
-       fnssh "github.com/apache/dubbo-kubernetes/cli/pkg/hub/ssh"
+       fnssh "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/ssh"
        "github.com/docker/cli/cli/config"
        "github.com/docker/go-connections/tlsconfig"
        "github.com/moby/moby/client"
diff --git a/cli/pkg/hub/credentials/credentials.go 
b/dubboctl/pkg/hub/credentials/credentials.go
similarity index 99%
rename from cli/pkg/hub/credentials/credentials.go
rename to dubboctl/pkg/hub/credentials/credentials.go
index f95f6291..b33bc54d 100644
--- a/cli/pkg/hub/credentials/credentials.go
+++ b/dubboctl/pkg/hub/credentials/credentials.go
@@ -29,7 +29,7 @@ import (
        "runtime"
        "strings"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/pusher"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/pusher"
        dockerConfig "github.com/containers/image/v5/pkg/docker/config"
        containersTypes "github.com/containers/image/v5/types"
        "github.com/docker/docker-credential-helpers/client"
diff --git a/cli/pkg/hub/credentials/prompt/prompt.go 
b/dubboctl/pkg/hub/credentials/prompt/prompt.go
similarity index 97%
rename from cli/pkg/hub/credentials/prompt/prompt.go
rename to dubboctl/pkg/hub/credentials/prompt/prompt.go
index 66f5e1d9..2a77c104 100644
--- a/cli/pkg/hub/credentials/prompt/prompt.go
+++ b/dubboctl/pkg/hub/credentials/prompt/prompt.go
@@ -28,8 +28,8 @@ import (
 
        "github.com/AlecAivazis/survey/v2"
        "github.com/AlecAivazis/survey/v2/terminal"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/credentials"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub/pusher"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/credentials"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/pusher"
        "golang.org/x/term"
 )
 
diff --git a/cli/pkg/hub/deployer/deploy.tpl 
b/dubboctl/pkg/hub/deployer/deploy.tpl
similarity index 100%
rename from cli/pkg/hub/deployer/deploy.tpl
rename to dubboctl/pkg/hub/deployer/deploy.tpl
diff --git a/cli/pkg/hub/deployer/deployer.go 
b/dubboctl/pkg/hub/deployer/deployer.go
similarity index 93%
rename from cli/pkg/hub/deployer/deployer.go
rename to dubboctl/pkg/hub/deployer/deployer.go
index 8ec426da..2927906a 100644
--- a/cli/pkg/hub/deployer/deployer.go
+++ b/dubboctl/pkg/hub/deployer/deployer.go
@@ -23,9 +23,9 @@ import (
        "os"
        template2 "text/template"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
 )
 
 const (
diff --git a/cli/pkg/hub/pusher/pusher.go b/dubboctl/pkg/hub/pusher/pusher.go
similarity index 98%
rename from cli/pkg/hub/pusher/pusher.go
rename to dubboctl/pkg/hub/pusher/pusher.go
index b52a7ee0..9bd10859 100644
--- a/cli/pkg/hub/pusher/pusher.go
+++ b/dubboctl/pkg/hub/pusher/pusher.go
@@ -29,8 +29,8 @@ import (
        "os"
        "regexp"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/hub"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
        "github.com/google/go-containerregistry/pkg/authn"
        "github.com/google/go-containerregistry/pkg/name"
        v1 "github.com/google/go-containerregistry/pkg/v1"
diff --git a/cli/pkg/hub/pusher/transport.go 
b/dubboctl/pkg/hub/pusher/transport.go
similarity index 100%
rename from cli/pkg/hub/pusher/transport.go
rename to dubboctl/pkg/hub/pusher/transport.go
diff --git a/cli/pkg/hub/ssh/agent.go b/dubboctl/pkg/hub/ssh/agent.go
similarity index 100%
rename from cli/pkg/hub/ssh/agent.go
rename to dubboctl/pkg/hub/ssh/agent.go
diff --git a/cli/pkg/hub/ssh/agent_windows.go 
b/dubboctl/pkg/hub/ssh/agent_windows.go
similarity index 100%
rename from cli/pkg/hub/ssh/agent_windows.go
rename to dubboctl/pkg/hub/ssh/agent_windows.go
diff --git a/cli/pkg/hub/ssh/dialer.go b/dubboctl/pkg/hub/ssh/dialer.go
similarity index 100%
rename from cli/pkg/hub/ssh/dialer.go
rename to dubboctl/pkg/hub/ssh/dialer.go
diff --git a/cli/pkg/hub/ssh/terminal.go b/dubboctl/pkg/hub/ssh/terminal.go
similarity index 100%
rename from cli/pkg/hub/ssh/terminal.go
rename to dubboctl/pkg/hub/ssh/terminal.go
diff --git a/cli/pkg/sdk/client.go b/dubboctl/pkg/sdk/client.go
similarity index 98%
rename from cli/pkg/sdk/client.go
rename to dubboctl/pkg/sdk/client.go
index 76997300..0acbc0e1 100644
--- a/cli/pkg/sdk/client.go
+++ b/dubboctl/pkg/sdk/client.go
@@ -25,8 +25,8 @@ import (
        "strings"
        "time"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
        "github.com/pkg/errors"
        "github.com/spf13/cobra"
        "gopkg.in/yaml.v3"
diff --git a/cli/pkg/sdk/dubbo/config.go b/dubboctl/pkg/sdk/dubbo/config.go
similarity index 100%
rename from cli/pkg/sdk/dubbo/config.go
rename to dubboctl/pkg/sdk/dubbo/config.go
diff --git a/cli/pkg/sdk/dubbo/dockerfile.go 
b/dubboctl/pkg/sdk/dubbo/dockerfile.go
similarity index 100%
rename from cli/pkg/sdk/dubbo/dockerfile.go
rename to dubboctl/pkg/sdk/dubbo/dockerfile.go
diff --git a/cli/pkg/sdk/repositories.go b/dubboctl/pkg/sdk/repositories.go
similarity index 100%
rename from cli/pkg/sdk/repositories.go
rename to dubboctl/pkg/sdk/repositories.go
diff --git a/cli/pkg/sdk/repository.go b/dubboctl/pkg/sdk/repository.go
similarity index 99%
rename from cli/pkg/sdk/repository.go
rename to dubboctl/pkg/sdk/repository.go
index 888d4951..71ad42bf 100644
--- a/cli/pkg/sdk/repository.go
+++ b/dubboctl/pkg/sdk/repository.go
@@ -19,7 +19,7 @@ package sdk
 import (
        "errors"
        "fmt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
        "github.com/go-git/go-billy/v5/memfs"
        "github.com/go-git/go-git/v5"
        "github.com/go-git/go-git/v5/plumbing"
diff --git a/cli/pkg/sdk/template.go b/dubboctl/pkg/sdk/template.go
similarity index 92%
rename from cli/pkg/sdk/template.go
rename to dubboctl/pkg/sdk/template.go
index 803209e3..5b9e8290 100644
--- a/cli/pkg/sdk/template.go
+++ b/dubboctl/pkg/sdk/template.go
@@ -18,8 +18,8 @@ package sdk
 
 import (
        "context"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
        "path"
 )
 
diff --git a/cli/pkg/sdk/templates.go b/dubboctl/pkg/sdk/templates.go
similarity index 95%
rename from cli/pkg/sdk/templates.go
rename to dubboctl/pkg/sdk/templates.go
index e0d1360d..2ccbbe4d 100644
--- a/cli/pkg/sdk/templates.go
+++ b/dubboctl/pkg/sdk/templates.go
@@ -18,8 +18,8 @@ package sdk
 
 import (
        "context"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/dubbo"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
        "strings"
 )
 
diff --git a/cli/pkg/sdk/templates_embedded.go 
b/dubboctl/pkg/sdk/templates_embedded.go
similarity index 90%
rename from cli/pkg/sdk/templates_embedded.go
rename to dubboctl/pkg/sdk/templates_embedded.go
index 53031e5c..93398a23 100644
--- a/cli/pkg/sdk/templates_embedded.go
+++ b/dubboctl/pkg/sdk/templates_embedded.go
@@ -19,8 +19,8 @@ package sdk
 import (
        "archive/zip"
        "bytes"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/sdk/tpl"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/util"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/tpl"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
 )
 
 func newEmbeddedTemplatesFS() util.Filesystem {
diff --git a/cli/pkg/sdk/tpl/default/manifest.yaml 
b/dubboctl/pkg/sdk/tpl/default/manifest.yaml
similarity index 100%
copy from cli/pkg/sdk/tpl/default/manifest.yaml
copy to dubboctl/pkg/sdk/tpl/default/manifest.yaml
diff --git a/cli/pkg/sdk/tpl/filesystem.go b/dubboctl/pkg/sdk/tpl/filesystem.go
similarity index 100%
rename from cli/pkg/sdk/tpl/filesystem.go
rename to dubboctl/pkg/sdk/tpl/filesystem.go
diff --git a/cli/pkg/sdk/tpl/zz_filesystem_generated.go 
b/dubboctl/pkg/sdk/tpl/zz_filesystem_generated.go
similarity index 100%
rename from cli/pkg/sdk/tpl/zz_filesystem_generated.go
rename to dubboctl/pkg/sdk/tpl/zz_filesystem_generated.go
diff --git a/cli/pkg/util/err.go b/dubboctl/pkg/util/err.go
similarity index 100%
rename from cli/pkg/util/err.go
rename to dubboctl/pkg/util/err.go
diff --git a/cli/pkg/util/file.go b/dubboctl/pkg/util/file.go
similarity index 100%
rename from cli/pkg/util/file.go
rename to dubboctl/pkg/util/file.go
diff --git a/cli/pkg/util/fs.go b/dubboctl/pkg/util/fs.go
similarity index 100%
rename from cli/pkg/util/fs.go
rename to dubboctl/pkg/util/fs.go
diff --git a/cli/pkg/util/path.go b/dubboctl/pkg/util/path.go
similarity index 100%
rename from cli/pkg/util/path.go
rename to dubboctl/pkg/util/path.go
diff --git a/cli/pkg/util/sortedset.go b/dubboctl/pkg/util/sortedset.go
similarity index 100%
rename from cli/pkg/util/sortedset.go
rename to dubboctl/pkg/util/sortedset.go
diff --git a/cli/pkg/validate/validate.go b/dubboctl/pkg/validate/validate.go
similarity index 99%
rename from cli/pkg/validate/validate.go
rename to dubboctl/pkg/validate/validate.go
index 4e97f575..369d5f7f 100644
--- a/cli/pkg/validate/validate.go
+++ b/dubboctl/pkg/validate/validate.go
@@ -25,7 +25,7 @@ import (
        "path/filepath"
        "strings"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        operator "github.com/apache/dubbo-kubernetes/operator/pkg/apis"
        operatorvalidate 
"github.com/apache/dubbo-kubernetes/operator/pkg/apis/validation"
        "github.com/apache/dubbo-kubernetes/pkg/config/validation"
diff --git a/cli/pkg/version/version.go b/dubboctl/pkg/version/version.go
similarity index 100%
rename from cli/pkg/version/version.go
rename to dubboctl/pkg/version/version.go
diff --git a/manifests/charts/README.md b/manifests/charts/README.md
index 5dbf6881..ee7de938 100644
--- a/manifests/charts/README.md
+++ b/manifests/charts/README.md
@@ -1,5 +1,17 @@
-# Dubbo Installer
+# Dubbo Helm Charts
 
-> WARNING: Do not use the files in this directory to install Dubbo
+The charts in this directory are the **single source of truth** for every
+supported install path:
 
-If you want to install Dubbo with Helm, instead please follow the Helm 
installation docs here.
\ No newline at end of file
+- `base`: cluster-wide resources (CRDs). Install first.
+- `dubbod`: the dubbod control plane (and optional CNI DaemonSet).
+
+Both install paths render exactly these charts:
+
+1. **Helm**: `helm install` the charts directly (see each chart's README).
+2. **dubboctl install**: the CLI embeds this directory and renders it locally,
+   layered with a profile from `manifests/profiles`.
+
+Any manifest change must be made here; do not fork rendered output elsewhere.
+CI lints and renders both charts on every PR (`make lint-helm`), and the kind
+smoke test installs them end-to-end (`make test-e2e`).
diff --git a/manifests/charts/dubbod/templates/deployment.yaml 
b/manifests/charts/dubbod/templates/deployment.yaml
index 5d206f3b..eb87a688 100644
--- a/manifests/charts/dubbod/templates/deployment.yaml
+++ b/manifests/charts/dubbod/templates/deployment.yaml
@@ -25,6 +25,7 @@
 {{- $remoteAccess := $multicluster.remoteAccess | default dict }}
 {{- $eastWestGateway := $multicluster.eastWestGateway | default dict }}
 {{- $guiPort := int (coalesce $gui.port $defaultGUI.port 26080) }}
+{{- $replicaCount := int (coalesce .Values.replicaCount $defaults.replicaCount 
1) }}
 {{- $remoteAccessCertificateHosts := $remoteAccess.certificateHosts | default 
$defaultRemoteAccess.certificateHosts | default (list) }}
 {{- $eastWestGateways := $eastWestGateway.gateways | default 
$defaultEastWestGateway.gateways | default (list) }}
 {{- $eastWestGatewayEntries := list }}
@@ -42,6 +43,7 @@ metadata:
     app: dubbod
     dubbo.apache.org/rev: default
 spec:
+  replicas: {{ $replicaCount }}
   selector:
     matchLabels:
       app: dubbod
@@ -51,6 +53,15 @@ spec:
         app: dubbod
     spec:
       serviceAccountName: dubbod
+      affinity:
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+            - weight: 100
+              podAffinityTerm:
+                topologyKey: kubernetes.io/hostname
+                labelSelector:
+                  matchLabels:
+                    app: dubbod
       containers:
         - name: execute
           image: "kdubbo/dubbod:debug"
diff --git a/cli/pkg/sdk/tpl/default/manifest.yaml 
b/manifests/charts/dubbod/templates/poddisruptionbudget.yaml
similarity index 62%
copy from cli/pkg/sdk/tpl/default/manifest.yaml
copy to manifests/charts/dubbod/templates/poddisruptionbudget.yaml
index 7b692152..a866544b 100644
--- a/cli/pkg/sdk/tpl/default/manifest.yaml
+++ b/manifests/charts/dubbod/templates/poddisruptionbudget.yaml
@@ -13,9 +13,20 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-schema_version: 0.0.1 # The version for manifest.yaml schema
-
-# The name used for this language pack repository when referenced
-# in the UX, and its version
-name: default
-version: 0.0.1
+{{- $defaults := .Values._internal_default_values_not_set | default dict }}
+{{- $replicaCount := int (coalesce .Values.replicaCount $defaults.replicaCount 
1) }}
+{{- if gt $replicaCount 1 }}
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+  name: dubbod
+  namespace: dubbo-system
+  labels:
+    app: dubbod
+    dubbo.apache.org/rev: {{ .Values.revision | default "default" }}
+spec:
+  minAvailable: 1
+  selector:
+    matchLabels:
+      app: dubbod
+{{- end }}
diff --git 
a/manifests/charts/dubbod/templates/validatingwebhookconfiguration.yaml 
b/manifests/charts/dubbod/templates/validatingwebhookconfiguration.yaml
index 115ddd85..85c304a2 100644
--- a/manifests/charts/dubbod/templates/validatingwebhookconfiguration.yaml
+++ b/manifests/charts/dubbod/templates/validatingwebhookconfiguration.yaml
@@ -22,6 +22,11 @@ metadata:
     app: dubbod
     dubbo.apache.org/rev: {{ .Values.revision | default "default" }}
 webhooks:
+  # clientConfig.caBundle and failurePolicy are intentionally not set here:
+  # the running dubbod owns both fields (it injects the CA bundle and manages
+  # the failure policy). Declaring them in the chart makes Helm claim field
+  # ownership and every subsequent `helm upgrade` fails with a server-side
+  # apply conflict against the "dubbod" field manager.
   - name: rev.validation.dubbo.apache.org
     admissionReviewVersions: ["v1"]
     clientConfig:
@@ -29,8 +34,6 @@ webhooks:
         name: dubbod
         namespace: dubbo-system
         path: "/validate"
-      caBundle: ""
-    failurePolicy: Ignore
     objectSelector:
       matchExpressions:
       - key: dubbo.apache.org/rev
@@ -56,8 +59,6 @@ webhooks:
         name: dubbod
         namespace: dubbo-system
         path: "/validate"
-      caBundle: ""
-    failurePolicy: Ignore
     rules:
     - operations:
       - CREATE
diff --git a/manifests/charts/dubbod/values.yaml 
b/manifests/charts/dubbod/values.yaml
index 765a487e..a815da30 100644
--- a/manifests/charts/dubbod/values.yaml
+++ b/manifests/charts/dubbod/values.yaml
@@ -16,6 +16,10 @@
 _internal_default_values_not_set:
   revision: ""
 
+  # Number of dubbod replicas. Set >= 2 for a highly available control plane;
+  # a PodDisruptionBudget (minAvailable: 1) is created automatically then.
+  replicaCount: 1
+
   global:
     proxy:
       clusterDomain: "cluster.local"
diff --git a/operator/cmd/cluster/install.go b/operator/cmd/cluster/install.go
index 50f1de7f..131c24fe 100644
--- a/operator/cmd/cluster/install.go
+++ b/operator/cmd/cluster/install.go
@@ -18,7 +18,7 @@ package cluster
 
 import (
        "fmt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/operator/pkg/install"
        "github.com/apache/dubbo-kubernetes/operator/pkg/render"
        "github.com/apache/dubbo-kubernetes/operator/pkg/util/clog"
diff --git a/operator/cmd/cluster/manifest.go b/operator/cmd/cluster/manifest.go
index 76092698..d90b9577 100644
--- a/operator/cmd/cluster/manifest.go
+++ b/operator/cmd/cluster/manifest.go
@@ -19,7 +19,7 @@ package cluster
 import (
        "cmp"
        "fmt"
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/operator/pkg/manifest"
        "github.com/apache/dubbo-kubernetes/operator/pkg/render"
        "github.com/apache/dubbo-kubernetes/operator/pkg/util/clog"
diff --git a/operator/cmd/cluster/uninstall.go 
b/operator/cmd/cluster/uninstall.go
index 36b9434d..fe009821 100644
--- a/operator/cmd/cluster/uninstall.go
+++ b/operator/cmd/cluster/uninstall.go
@@ -20,7 +20,7 @@ import (
        "fmt"
        "os"
 
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/operator/pkg/render"
        "github.com/apache/dubbo-kubernetes/operator/pkg/uninstall"
        "github.com/apache/dubbo-kubernetes/operator/pkg/util/clog"
diff --git a/operator/cmd/cluster/upgrade.go b/operator/cmd/cluster/upgrade.go
index 892aed0a..b95a3d92 100644
--- a/operator/cmd/cluster/upgrade.go
+++ b/operator/cmd/cluster/upgrade.go
@@ -17,7 +17,7 @@
 package cluster
 
 import (
-       "github.com/apache/dubbo-kubernetes/cli/pkg/cli"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/operator/pkg/util/clog"
 
        // "github.com/apache/dubbo-kubernetes/operator/pkg/util/clog"
diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh
new file mode 100755
index 00000000..20ec33ab
--- /dev/null
+++ b/tests/e2e/run.sh
@@ -0,0 +1,149 @@
+#!/usr/bin/env 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.
+
+# End-to-end smoke test: builds the dubbod image, starts a kind cluster,
+# installs the base + dubbod Helm charts, and asserts that the control
+# plane serves xDS state for workloads and ServiceEntry configuration.
+#
+# Requirements: docker, kind, kubectl, helm.
+#
+# Environment knobs:
+#   CLUSTER_NAME    kind cluster name           (default: dubbo-e2e)
+#   IMAGE           dubbod image to build/load  (default: kdubbo/dubbod:debug)
+#   DUBBOD_REPLICAS control plane replicas      (default: 2, exercises HA)
+#   SKIP_BUILD      set to 1 to reuse an already-built ${IMAGE}
+#   KEEP_CLUSTER    set to 1 to keep the kind cluster after the run
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+CLUSTER_NAME="${CLUSTER_NAME:-dubbo-e2e}"
+IMAGE="${IMAGE:-kdubbo/dubbod:debug}"
+DUBBOD_REPLICAS="${DUBBOD_REPLICAS:-2}"
+SYSTEM_NS="dubbo-system"
+APP_NS="e2e"
+KUBECTL=(kubectl --context "kind-${CLUSTER_NAME}")
+
+log() { echo "--- $*"; }
+
+fail() {
+  echo "FAIL: $*" >&2
+  echo "--- diagnostics: pods ---" >&2
+  "${KUBECTL[@]}" get pods -A -o wide >&2 || true
+  echo "--- diagnostics: dubbod logs ---" >&2
+  "${KUBECTL[@]}" -n "${SYSTEM_NS}" logs deploy/dubbod --tail=100 >&2 || true
+  exit 1
+}
+
+cleanup() {
+  if [[ -n "${PF_PID:-}" ]]; then kill "${PF_PID}" 2>/dev/null || true; fi
+  if [[ "${KEEP_CLUSTER:-0}" != "1" ]]; then
+    kind delete cluster --name "${CLUSTER_NAME}" || true
+  fi
+}
+trap cleanup EXIT
+
+if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
+  log "building ${IMAGE}"
+  docker build -f "${ROOT}/dubbod/discovery/docker/dockerfile.dubbod" -t 
"${IMAGE}" "${ROOT}"
+fi
+
+if ! kind get clusters 2>/dev/null | grep -qx "${CLUSTER_NAME}"; then
+  log "creating kind cluster ${CLUSTER_NAME}"
+  kind create cluster --name "${CLUSTER_NAME}" --wait 120s
+fi
+
+log "loading ${IMAGE} into kind"
+kind load docker-image "${IMAGE}" --name "${CLUSTER_NAME}"
+
+log "installing Gateway API CRDs"
+# Pin to the sigs.k8s.io/gateway-api version in go.mod.
+GATEWAY_API_VERSION="${GATEWAY_API_VERSION:-v1.4.1}"
+"${KUBECTL[@]}" apply -f 
"https://github.com/kubernetes-sigs/gateway-api/releases/download/${GATEWAY_API_VERSION}/standard-install.yaml";
+
+log "installing base chart (CRDs)"
+helm upgrade --install dubbo-base "${ROOT}/manifests/charts/base" \
+  --kube-context "kind-${CLUSTER_NAME}" \
+  -n "${SYSTEM_NS}" --create-namespace
+
+install_dubbod() {
+  # The CNI daemonset needs privileged host access; keep the smoke test to the
+  # control plane itself.
+  helm upgrade --install dubbod "${ROOT}/manifests/charts/dubbod" \
+    --kube-context "kind-${CLUSTER_NAME}" \
+    -n "${SYSTEM_NS}" \
+    --set global.proxyless.cni.enabled=false \
+    --set replicaCount="${DUBBOD_REPLICAS}"
+}
+
+log "installing dubbod chart (${DUBBOD_REPLICAS} replicas)"
+install_dubbod
+
+log "waiting for dubbod rollout"
+"${KUBECTL[@]}" -n "${SYSTEM_NS}" rollout status deploy/dubbod --timeout=300s \
+  || fail "dubbod deployment did not become ready"
+
+if [[ "${DUBBOD_REPLICAS}" -gt 1 ]]; then
+  log "asserting PodDisruptionBudget exists for HA"
+  "${KUBECTL[@]}" -n "${SYSTEM_NS}" get pdb dubbod >/dev/null \
+    || fail "PodDisruptionBudget dubbod not found with 
replicaCount=${DUBBOD_REPLICAS}"
+fi
+
+# Regression: re-running helm upgrade against a live control plane must not
+# hit server-side apply conflicts on the fields dubbod manages at runtime
+# (webhook caBundle / failurePolicy).
+log "re-running helm upgrade against the live control plane"
+install_dubbod || fail "helm upgrade over a running dubbod failed (SSA field 
conflict?)"
+
+log "deploying sample workload (httpbin)"
+"${KUBECTL[@]}" create namespace "${APP_NS}" --dry-run=client -o yaml | 
"${KUBECTL[@]}" apply -f -
+"${KUBECTL[@]}" -n "${APP_NS}" apply -f "${ROOT}/samples/httpbin/httpbin.yaml"
+"${KUBECTL[@]}" -n "${APP_NS}" rollout status deploy/httpbin --timeout=300s \
+  || fail "httpbin deployment did not become ready"
+
+log "applying ServiceEntry through the validating webhook"
+"${KUBECTL[@]}" -n "${APP_NS}" apply -f 
"${ROOT}/tests/e2e/testdata/serviceentry.yaml" \
+  || fail "valid ServiceEntry was rejected"
+
+log "port-forwarding dubbod monitoring port"
+"${KUBECTL[@]}" -n "${SYSTEM_NS}" port-forward deploy/dubbod 18080:8080 
>/dev/null 2>&1 &
+PF_PID=$!
+
+probe() { curl -sf --max-time 5 "http://127.0.0.1:18080$1";; }
+
+# The registry and config propagate asynchronously; retry before failing.
+retry() {
+  local desc="$1"; shift
+  for _ in $(seq 1 30); do
+    if "$@" >/dev/null 2>&1; then return 0; fi
+    sleep 2
+  done
+  fail "timed out waiting for: ${desc}"
+}
+
+check_registry_service() { probe /debug/registryz | grep -q 
"httpbin.${APP_NS}.svc"; }
+check_registry_serviceentry() { probe /debug/registryz | grep -q 
"external.example.com"; }
+check_metrics() { probe /metrics | grep -q "^dubbod_"; }
+
+retry "monitoring endpoint up" probe /version
+log "asserting /metrics exposes dubbod metrics"
+retry "dubbod metrics" check_metrics
+log "asserting httpbin service is in the registry"
+retry "httpbin in /debug/registryz" check_registry_service
+log "asserting ServiceEntry host is in the registry"
+retry "ServiceEntry in /debug/registryz" check_registry_serviceentry
+
+log "e2e smoke test passed"
diff --git a/cli/pkg/sdk/tpl/default/manifest.yaml 
b/tests/e2e/testdata/serviceentry.yaml
similarity index 75%
copy from cli/pkg/sdk/tpl/default/manifest.yaml
copy to tests/e2e/testdata/serviceentry.yaml
index 7b692152..03d63386 100644
--- a/cli/pkg/sdk/tpl/default/manifest.yaml
+++ b/tests/e2e/testdata/serviceentry.yaml
@@ -13,9 +13,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-schema_version: 0.0.1 # The version for manifest.yaml schema
-
-# The name used for this language pack repository when referenced
-# in the UX, and its version
-name: default
-version: 0.0.1
+apiVersion: networking.dubbo.apache.org/v1alpha3
+kind: ServiceEntry
+metadata:
+  name: e2e-external
+spec:
+  hosts:
+    - external.example.com
+  location: MESH_EXTERNAL
+  resolution: DNS
+  ports:
+    - number: 80
+      name: http
+      protocol: HTTP
diff --git a/tools/make/build.mk b/tools/make/build.mk
new file mode 100644
index 00000000..28a2c81a
--- /dev/null
+++ b/tools/make/build.mk
@@ -0,0 +1,43 @@
+# 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.
+
+# Binary name -> main package mapping. Adding a binary is one line here plus
+# appending it to BINARIES.
+BINARIES            := dubboctl dubbod dubbo-cni
+main_path_dubboctl  := dubboctl/main.go
+main_path_dubbod    := dubbod/discovery/cmd/main.go
+main_path_dubbo-cni := cni/main.go
+
+##@ Build
+
+.PHONY: build
+build: $(addprefix build-,$(BINARIES)) ## Build all binaries into bin/ (host 
OS/arch; override with GOOS=/GOARCH=). Single binary: make build-<name>.
+
+.PHONY: $(addprefix build-,$(BINARIES))
+$(addprefix build-,$(BINARIES)): build-%:
+       $(GO_BUILD) -o $(BIN_DIR)/$* $(main_path_$*)
+
+.PHONY: docker-build
+docker-build: ## Build the dubbod container image (override with 
HUB=/IMAGE_TAG=).
+       docker build -f dubbod/discovery/docker/dockerfile.dubbod -t $(IMAGE) .
+
+.PHONY: clone-sample
+clone-sample: ## Copy samples/ next to the binaries for release packaging.
+       mkdir -p $(BIN_DIR)
+       cp -r samples $(BIN_DIR)/samples
+
+.PHONY: clean
+clean: ## Remove build artifacts and coverage output.
+       rm -rf $(BIN_DIR) coverage.txt
diff --git a/tools/make/common.mk b/tools/make/common.mk
new file mode 100644
index 00000000..819df27e
--- /dev/null
+++ b/tools/make/common.mk
@@ -0,0 +1,49 @@
+# 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.
+
+# 
==============================================================================
+# Shared configuration (override on the command line, e.g. `make build 
GOOS=linux`)
+# 
==============================================================================
+
+GOOS        ?= $(shell go env GOOS)
+GOARCH      ?= $(shell go env GOARCH)
+GIT_VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || 
echo unknown)
+BIN_DIR     ?= bin
+
+GOTESTFLAGS           ?= -race
+GOLANGCI_LINT_VERSION ?= v2.6.2
+
+HUB       ?= kdubbo
+IMAGE_TAG ?= debug
+IMAGE     ?= $(HUB)/dubbod:$(IMAGE_TAG)
+
+VERSION_PKG := github.com/apache/dubbo-kubernetes/pkg/version
+LDFLAGS     ?= -X $(VERSION_PKG).gitTag=$(GIT_VERSION)
+GO_BUILD     = CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -ldflags 
"$(LDFLAGS)"
+
+CHARTS := manifests/charts/base manifests/charts/dubbod
+
+.DEFAULT_GOAL := build
+
+##@ General
+
+.PHONY: help
+help: ## Display this help.
+       @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make 
\033[36m<target>\033[0m\n"} \
+               /^[a-zA-Z_0-9%-]+:.*?##/ { printf "  \033[36m%-16s\033[0m 
%s\n", $$1, $$2 } \
+               /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' 
$(MAKEFILE_LIST)
+
+.PHONY: verify
+verify: check-fmt lint test lint-helm ## Everything fast enough to run before 
pushing.
diff --git a/tools/make/lint.mk b/tools/make/lint.mk
new file mode 100644
index 00000000..6dae36a2
--- /dev/null
+++ b/tools/make/lint.mk
@@ -0,0 +1,73 @@
+# 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.
+
+##@ Lint / Format
+
+.PHONY: lint
+lint: lint-go lint-shell ## Run Go and shell linters.
+
+.PHONY: lint-go
+lint-go: ## Run golangci-lint.
+       @if ! command -v golangci-lint >/dev/null 2>&1; then \
+               echo "golangci-lint not found; install $(GOLANGCI_LINT_VERSION) 
from https://golangci-lint.run/welcome/install/";; \
+               exit 1; \
+       fi
+       golangci-lint run ./...
+
+.PHONY: lint-shell
+lint-shell: ## Run shellcheck over all shell scripts (skipped if not 
installed).
+       @if command -v shellcheck >/dev/null 2>&1; then \
+               find . -name '*.sh' -not -path './vendor/*' -not -path 
'./$(BIN_DIR)/*' -print0 | xargs -0 -r shellcheck; \
+       else \
+               echo "shellcheck not found, skipping shell lint"; \
+       fi
+
+.PHONY: lint-helm
+lint-helm: ## Lint and render the Helm charts.
+       @for chart in $(CHARTS); do \
+               helm lint $$chart || exit 1; \
+               helm template test-release $$chart >/dev/null || exit 1; \
+       done
+
+.PHONY: fmt
+fmt: ## Format all Go sources with gofmt -s.
+       gofmt -s -w .
+
+.PHONY: check-fmt
+check-fmt: ## Fail if any Go source needs gofmt -s.
+       @out="$$(gofmt -s -l .)"; \
+       if [ -n "$$out" ]; then \
+               echo "The following files need 'gofmt -s':"; \
+               echo "$$out"; \
+               exit 1; \
+       fi
+
+##@ Hygiene gates (CI runs these; they must leave the tree clean)
+
+.PHONY: tidy
+tidy: ## Run go mod tidy.
+       go mod tidy
+
+.PHONY: check-clean-repo
+check-clean-repo: ## Fail if the working tree is dirty.
+       @if [ -n "$$(git status --porcelain)" ]; then \
+               echo "The working tree is dirty after running 
generators/tidy:"; \
+               git status --porcelain; \
+               git diff; \
+               exit 1; \
+       fi
+
+.PHONY: check-tidy
+check-tidy: tidy check-clean-repo ## go mod tidy, then fail if it changed 
anything.
diff --git a/cli/pkg/sdk/tpl/default/manifest.yaml b/tools/make/test.mk
similarity index 61%
rename from cli/pkg/sdk/tpl/default/manifest.yaml
rename to tools/make/test.mk
index 7b692152..235b3a05 100644
--- a/cli/pkg/sdk/tpl/default/manifest.yaml
+++ b/tools/make/test.mk
@@ -5,7 +5,7 @@
 # (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
+#      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,
@@ -13,9 +13,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-schema_version: 0.0.1 # The version for manifest.yaml schema
+##@ Test
 
-# The name used for this language pack repository when referenced
-# in the UX, and its version
-name: default
-version: 0.0.1
+.PHONY: test
+test: ## Run unit tests (GOTESTFLAGS defaults to -race).
+       go test $(GOTESTFLAGS) ./...
+
+.PHONY: test-coverage
+test-coverage: ## Run unit tests and write coverage.txt.
+       go test $(GOTESTFLAGS) -coverprofile=coverage.txt -covermode=atomic 
./...
+
+.PHONY: test-e2e
+test-e2e: ## Run the kind smoke test (needs docker, kind, kubectl, helm; knobs 
in tests/e2e/run.sh).
+       bash tests/e2e/run.sh

Reply via email to