Repository: kubeclipper/kubeclipper
Pull request #285 (2022-10-20 11:24:10)

fix: update kcctl use online deploy config,remove local deploy config
<!-- Thanks for sending a pull request! Here are some tips for you:

1. If you want **faster** PR reviews, read how: https://github.com/kubeclipper/community/blob/master/developer-guide/development/the-pr-author-guide-to-getting-through-code-review.md
2. In case you want to know how your PR got reviewed, read: https://github.com/kubeclipper/community/blob/master/developer-guide/development/code-review-guide.md
3. Here are some coding convetions followed by Kubeclipper community: https://github.com/kubeclipper/community/blob/master/developer-guide/development/coding-conventions.md
-->

### What type of PR is this?
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->
/kind bug

### What this PR does / why we need it:

### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

### Special notes for reviewers:
```
1.kcctl use online deploy-config,except clean cmd，clean priority use online deploy-config,if get failed,change to use local deploy-config
2.all cmd remove deploy-config flag,except clean.
3.deploy、join、drain cmd update deploy-config，and dump a backup to local.
4.examples update
```

### Does this PR introduced a user-facing change?
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md
-->
```release-note
None
```

### Additional documentation, usage docs, etc.:
<!--
This section can be blank if this pull request does not require a release note.
Please use the following format for linking documentation or pass the
section below:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```

kubeclipper-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubeclipper/kubeclipper/pull/285#" title="Author self-approved">lixd</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **wu-wenxiang** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @wu-wenxiang` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubeclipper%2Fkubeclipper).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubeclipper/kubeclipper/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["wu-wenxiang"]} -->
lixd: /assign @x893675 
x893675: /lgtm
/approve
kubeclipper-bot: LGTM label has been added.  <details>Git tree hash: c9727250fcdda7f3a0c549db2507f4f837fa9e6c</details>
kubeclipper-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubeclipper/kubeclipper/pull/285#" title="Author self-approved">lixd</a>*, *<a href="https://github.com/kubeclipper/kubeclipper/pull/285#issuecomment-1286403921" title="Approved">x893675</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubeclipper%2Fkubeclipper).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubeclipper/kubeclipper/blob/master/OWNERS)~~ [x893675]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/cmd/kcctl/app/options/options.go b/cmd/kcctl/app/options/options.go
index 78149a0..67b50f8 100644
--- a/cmd/kcctl/app/options/options.go
+++ b/cmd/kcctl/app/options/options.go
@@ -26,12 +26,11 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/kubeclipper/kubeclipper/pkg/auditing/option"
-	"github.com/kubeclipper/kubeclipper/pkg/authentication/options"
-
 	"github.com/subosito/gotenv"
 	"k8s.io/apimachinery/pkg/util/sets"
 
+	"github.com/kubeclipper/kubeclipper/pkg/auditing/option"
+	"github.com/kubeclipper/kubeclipper/pkg/authentication/options"
 	"github.com/kubeclipper/kubeclipper/pkg/utils/sliceutil"
 
 	"github.com/kubeclipper/kubeclipper/pkg/utils/autodetection"
@@ -336,6 +335,12 @@ func (c *DeployConfig) MergeDeployOptions() {
 
 }
 
+/*
+NOTE: deploy cmd use local deploy-config,others cmd use online deploy-config.
+and clean cmd use online deploy-config first,if can't get online deploy-config,
+then use local deploy-config as a downgrade.
+*/
+
 func (c *DeployConfig) Complete() error {
 	if c.Config == "" {
 		return nil
diff --git a/pkg/cli/clean/clean.go b/pkg/cli/clean/clean.go
index e51a5e0..1f4d145 100644
--- a/pkg/cli/clean/clean.go
+++ b/pkg/cli/clean/clean.go
@@ -19,11 +19,16 @@
 package clean
 
 import (
+	"context"
 	"fmt"
+	"path/filepath"
 
+	"github.com/pkg/errors"
 	"k8s.io/apimachinery/pkg/util/sets"
 
+	"github.com/kubeclipper/kubeclipper/pkg/cli/deploy"
 	"github.com/kubeclipper/kubeclipper/pkg/cli/utils"
+	"github.com/kubeclipper/kubeclipper/pkg/simple/client/kc"
 
 	"github.com/kubeclipper/kubeclipper/pkg/cli/sudo"
 	"github.com/kubeclipper/kubeclipper/pkg/utils/sshutils"
@@ -37,35 +42,37 @@ import (
 
 const (
 	longDescription = `
-  Uninstall kubeclipper Platform from deploy-config.yaml or cmd flags.
+  Uninstall kubeclipper Platform .
 
-  Uninstall all kubeclipper plug-ins.
-  You can choose to uninstall it directly or using a configuration file.`
+  Uninstall all kubeclipper plug-ins.`
 	cleanExample = `
-  # Uninstall the entire kubeclipper platform use default deploy config(~/.kc/deploy-config.yaml).
+  # Uninstall the entire kubeclipper platform.
   kcctl clean --all
   kcctl clean -A
 
   # Mock uninstall,without -A flag will only do preCheck and config check.
   kcctl clean
-  kcctl clean --config ~/.kc/deploy-config.yaml
 
-  # Specify the deploy-config.yaml path
-  kcctl clean --config ~/.kc/deploy-config.yaml -A
+  # Uninstall the entire kubeclipper platform,use specify the config.
+  kcctl clean -A --config ~/.kc/config
 
   Please read 'kcctl clean -h' get more clean flags`
 )
 
 type CleanOptions struct {
 	options.IOStreams
+	cliOpts      *options.CliOptions
 	deployConfig *options.DeployConfig
+	client       *kc.Client
 	cleanAll     bool
-	allNodes     []string
+
+	allNodes []string
 }
 
 func NewCleanOptions(streams options.IOStreams) *CleanOptions {
 	return &CleanOptions{
 		IOStreams:    streams,
+		cliOpts:      options.NewCliOptions(),
 		deployConfig: options.NewDeployOptions(),
 	}
 }
@@ -73,14 +80,13 @@ func NewCleanOptions(streams options.IOStreams) *CleanOptions {
 func NewCmdClean(streams options.IOStreams) *cobra.Command {
 	o := NewCleanOptions(streams)
 	cmd := &cobra.Command{
-		Use:                   "clean [(--config [<configFilePath>])] [flags]",
+		Use:                   "clean [flags]",
 		DisableFlagsInUseLine: true,
 		Short:                 "Uninstall kubeclipper platform",
 		Long:                  longDescription,
 		Example:               cleanExample,
 		Run: func(cmd *cobra.Command, args []string) {
 			utils.CheckErr(o.Complete())
-			o.ValidateArgs()
 			if !o.preCheck() {
 				return
 			}
@@ -88,21 +94,37 @@ func NewCmdClean(streams options.IOStreams) *cobra.Command {
 			fmt.Printf("\033[1;40;36m%s\033[0m\n", options.Contact)
 		},
 	}
-	cmd.Flags().StringVarP(&o.deployConfig.Config, "config", "c", options.DefaultDeployConfigPath, "Path to the deploy config file to use for clean.")
+	o.cliOpts.AddFlags(cmd.Flags())
 	cmd.Flags().BoolVarP(&o.cleanAll, "all", "A", o.cleanAll, "clean all components for kubeclipper")
 	return cmd
 }
 
 func (c *CleanOptions) Complete() error {
+	// config Complete
+	var err error
+	if err = c.cliOpts.Complete(); err != nil {
+		return err
+	}
+	c.client, err = kc.FromConfig(c.cliOpts.ToRawConfig())
+	if err != nil {
+		return err
+	}
+
+	// deploy-config Complete
+	dc, err := deploy.GetDeployConfig(context.Background(), c.client, false)
+	if err != nil {
+		logger.Warn("get online deploy-config failed,downgrade to use local deploy-config,reason:", err)
+		if err = c.deployConfig.Complete(); err != nil {
+			return errors.WithMessage(err, "run with local deploy-config failed")
+		}
+	} else {
+		c.deployConfig = dc
+	}
 	c.allNodes = sets.NewString().
 		Insert(c.deployConfig.ServerIPs...).
 		Insert(c.deployConfig.Agents.ListIP()...).
 		List()
-	return c.deployConfig.Complete()
-}
-
-func (c *CleanOptions) ValidateArgs() {
-	// TODO: add validate
+	return nil
 }
 
 func (c *CleanOptions) preCheck() bool {
@@ -117,6 +139,7 @@ func (c *CleanOptions) RunClean() {
 		c.cleanKcConsole()
 		c.cleanBinaries()
 		c.cleanKcEnv()
+		c.cleanKcConfig()
 	}
 	logger.Info("clean successful")
 }
@@ -137,7 +160,7 @@ func (c *CleanOptions) cleanKcAgent() {
 	for _, cmd := range cmdList {
 		err := sshutils.CmdBatchWithSudo(c.deployConfig.SSHConfig, c.deployConfig.Agents.ListIP(), cmd, sshutils.DefaultWalk)
 		if err != nil {
-			logger.V(2).Error(err)
+			logger.Warn("clean kc agent failed,reason: ", err)
 		}
 	}
 }
@@ -157,7 +180,7 @@ func (c *CleanOptions) cleanKcProxy() {
 	for _, cmd := range cmdList {
 		err := sshutils.CmdBatchWithSudo(c.deployConfig.SSHConfig, c.deployConfig.Proxys, cmd, sshutils.DefaultWalk)
 		if err != nil {
-			logger.V(2).Error(err)
+			logger.Warn("clean kc proxy failed,reason: ", err)
 		}
 	}
 }
@@ -182,7 +205,7 @@ func (c *CleanOptions) cleanKcServer() {
 	for _, cmd := range cmdList {
 		err := sshutils.CmdBatchWithSudo(c.deployConfig.SSHConfig, c.deployConfig.ServerIPs, cmd, sshutils.DefaultWalk)
 		if err != nil {
-			logger.V(2).Error(err)
+			logger.Warn("clean kc server failed,reason: ", err)
 		}
 	}
 }
@@ -202,7 +225,7 @@ func (c *CleanOptions) cleanKcConsole() {
 	for _, cmd := range cmdList {
 		err := sshutils.CmdBatchWithSudo(c.deployConfig.SSHConfig, c.deployConfig.ServerIPs, cmd, sshutils.DefaultWalk)
 		if err != nil {
-			logger.V(2).Error(err)
+			logger.Warn("clean kc console failed,reason: ", err)
 		}
 	}
 }
@@ -214,13 +237,13 @@ func (c *CleanOptions) cleanBinaries() {
 	}
 
 	cmdList := []string{
-		"rm -rf /usr/local/bin/kube* && rm -rf /usr/local/bin/etcd* && rm -rf /usr/local/bin/kcctl && rm -rf /usr/local/bin/caddy",
+		"rm -rf /usr/local/bin/kubeclipper* && rm -rf /usr/local/bin/etcd*  && rm -rf /usr/local/bin/caddy",
 	}
 
 	for _, cmd := range cmdList {
 		err := sshutils.CmdBatchWithSudo(c.deployConfig.SSHConfig, c.allNodes, cmd, sshutils.DefaultWalk)
 		if err != nil {
-			logger.V(2).Error(err)
+			logger.Warn("clean kc binary failed,reason: ", err)
 		}
 	}
 }
@@ -238,7 +261,13 @@ func (c *CleanOptions) cleanKcEnv() {
 	for _, cmd := range cmdList {
 		err := sshutils.CmdBatchWithSudo(c.deployConfig.SSHConfig, c.deployConfig.ServerIPs, cmd, sshutils.DefaultWalk)
 		if err != nil {
-			logger.V(2).Error(err)
+			logger.Warn("clean kc env failed,reason: ", err)
 		}
 	}
 }
+
+func (c *CleanOptions) cleanKcConfig() {
+	if err := sshutils.Cmd("rm", "-rf", filepath.Dir(options.DefaultDeployConfigPath)); err != nil {
+		logger.Warn("clean kc config failed,reason: ", err)
+	}
+}
diff --git a/pkg/cli/join/agent.go b/pkg/cli/deploy/agent.go
similarity index 99%
rename from pkg/cli/join/agent.go
rename to pkg/cli/deploy/agent.go
index d25a873..21332e9 100644
--- a/pkg/cli/join/agent.go
+++ b/pkg/cli/deploy/agent.go
@@ -16,7 +16,7 @@
  *
  */
 
-package join
+package deploy
 
 import (
 	"fmt"
diff --git a/pkg/cli/deploy/deploy.go b/pkg/cli/deploy/deploy.go
index ee421a6..e0ea218 100644
--- a/pkg/cli/deploy/deploy.go
+++ b/pkg/cli/deploy/deploy.go
@@ -56,8 +56,6 @@ import (
 	"github.com/sethvargo/go-password/password"
 	"gopkg.in/yaml.v2"
 
-	"github.com/kubeclipper/kubeclipper/pkg/cli/join"
-
 	"github.com/kubeclipper/kubeclipper/pkg/cli/sudo"
 
 	"github.com/kubeclipper/kubeclipper/pkg/cli/config"
@@ -202,7 +200,7 @@ func (d *DeployOptions) Complete() error {
 
 	// if specify config，ignore flags.
 	if d.deployConfig.Config == "" {
-		if d.deployConfig.Agents, err = join.BuildAgent(d.agents, d.fips, d.deployConfig.DefaultRegion); err != nil {
+		if d.deployConfig.Agents, err = BuildAgent(d.agents, d.fips, d.deployConfig.DefaultRegion); err != nil {
 			return err
 		}
 	}
diff --git a/pkg/cli/deploy/deploy_config.go b/pkg/cli/deploy/deploy_config.go
new file mode 100644
index 0000000..f6feb55
--- /dev/null
+++ b/pkg/cli/deploy/deploy_config.go
@@ -0,0 +1,75 @@
+package deploy
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/pkg/errors"
+	"gopkg.in/yaml.v2"
+
+	"github.com/kubeclipper/kubeclipper/cmd/kcctl/app/options"
+	"github.com/kubeclipper/kubeclipper/pkg/component"
+	"github.com/kubeclipper/kubeclipper/pkg/component/utils"
+	"github.com/kubeclipper/kubeclipper/pkg/constatns"
+	"github.com/kubeclipper/kubeclipper/pkg/simple/client/kc"
+)
+
+// GetDeployConfig get online deploy config and dump to local if we need.
+func GetDeployConfig(ctx context.Context, cli *kc.Client, dump bool) (*options.DeployConfig, error) {
+	dc := new(options.DeployConfig)
+	configMap, err := cli.DescribeConfigMap(ctx, constatns.DeployConfigConfigMapName)
+	if err != nil {
+		return nil, err
+	}
+	if len(configMap.Items) == 0 {
+		return nil, fmt.Errorf("configmap %s not found in server", constatns.DeployConfigConfigMapName)
+	}
+
+	data := configMap.Items[0].Data[constatns.DeployConfigConfigMapKey]
+	if err = yaml.Unmarshal([]byte(data), dc); err != nil {
+		return nil, err
+	}
+	if dc.Agents == nil {
+		dc.Agents = make(options.Agents)
+	}
+
+	if !dump {
+		if err = dc.Write(); err != nil {
+			return nil, errors.WithMessage(err, "dump local deploy-config")
+		}
+	}
+	return dc, nil
+}
+
+// UpdateDeployConfig update online deploy config and dump to local if we need.
+func UpdateDeployConfig(ctx context.Context, cli *kc.Client, deployConfig *options.DeployConfig, dump bool) error {
+	if dump {
+		if err := deployConfig.Write(); err != nil {
+			return errors.WithMessage(err, "dump local deploy config")
+		}
+	}
+	marshal, err := yaml.Marshal(deployConfig)
+	if err != nil {
+		return err
+	}
+	timeout, cancelFunc := context.WithTimeout(ctx, time.Second*10)
+	defer cancelFunc()
+
+	// kcctl join、drain will update online config after cmd success,so update with retry to avoid data inconsistency
+	return utils.RetryFunc(timeout, component.Options{DryRun: false}, time.Second, "updateDeployConfig", func(ctx context.Context, opts component.Options) error {
+		configMap, err := cli.DescribeConfigMap(ctx, constatns.DeployConfigConfigMapName)
+		if err != nil {
+			return errors.WithMessage(err, "get configmap")
+		}
+		if len(configMap.Items) == 0 {
+			return fmt.Errorf("configmap %s not found in server", constatns.DeployConfigConfigMapName)
+		}
+		cm := configMap.Items[0]
+		cm.Data[constatns.DeployConfigConfigMapKey] = string(marshal)
+		if _, err = cli.UpdateConfigMap(ctx, &cm); err != nil {
+			return errors.WithMessage(err, "update online deploy config")
+		}
+		return nil
+	})
+}
diff --git a/pkg/cli/drain/drain.go b/pkg/cli/drain/drain.go
index 7b441b1..cd35766 100644
--- a/pkg/cli/drain/drain.go
+++ b/pkg/cli/drain/drain.go
@@ -27,6 +27,8 @@ import (
 	"github.com/pkg/errors"
 	"k8s.io/apimachinery/pkg/util/sets"
 
+	"github.com/kubeclipper/kubeclipper/pkg/cli/deploy"
+
 	"github.com/kubeclipper/kubeclipper/pkg/cli/utils"
 	"github.com/kubeclipper/kubeclipper/pkg/scheme/common"
 	v1 "github.com/kubeclipper/kubeclipper/pkg/scheme/core/v1"
@@ -48,11 +50,11 @@ const (
  
   Now only support drain kc-agent node, so the --agent parameter must be valid.`
 	drainExample = `
-  # Drain kc-agent from kubeclipper cluster use default deploy-config(~/.kc/deploy-config.yaml) and config(~/.kc/config).
+  # Drain kc-agent from kubeclipper cluster use default config(~/.kc/config).
   kcctl drain --agent d55e10ec-4e7e-4ce9-9ce7-eb491ddc7bfa
 
-  # Drain kc-agent from kubeclipper cluster specify deploy config and config.
-  kcctl drain --agent d55e10ec-4e7e-4ce9-9ce7-eb491ddc7bfa --deploy-config /root/.kc/deploy-config.yaml --config /root/.kc/config
+  # Drain kc-agent from kubeclipper cluster specify config.
+  kcctl drain --agent d55e10ec-4e7e-4ce9-9ce7-eb491ddc7bfa --config /root/.kc/config
 
   # Force drain kc-agent which is in used from kubeclipper cluster
   kcctl drain  --force --agent=d55e10ec-4e7e-4ce9-9ce7-eb491ddc7bfa
@@ -98,7 +100,6 @@ func NewCmdDrain(streams options.IOStreams) *cobra.Command {
 
 	o.cliOpts.AddFlags(cmd.Flags())
 	cmd.Flags().StringSliceVar(&o.agents, "agent", o.agents, "drain agent node ID.")
-	cmd.Flags().StringVar(&o.deployConfig.Config, "deploy-config", options.DefaultDeployConfigPath, "kcctl deploy config path")
 	cmd.Flags().BoolVarP(&o.force, "force", "F", o.force, "force delete in used node.")
 
 	utils.CheckErr(cmd.RegisterFlagCompletionFunc("agent", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
@@ -122,8 +123,8 @@ func (c *DrainOptions) listNode(toComplete string) []string {
 	}
 	set := sets.NewString()
 	for _, node := range nodes.Items {
-		if strings.HasPrefix(node.Status.Ipv4DefaultIP, toComplete) {
-			set.Insert(node.Status.Ipv4DefaultIP)
+		if strings.HasPrefix(node.Name, toComplete) {
+			set.Insert(node.Name)
 		}
 	}
 
@@ -133,18 +134,21 @@ func (c *DrainOptions) listNode(toComplete string) []string {
 func (c *DrainOptions) Complete() error {
 	var err error
 
-	// deploy config Complete
-	if err = c.deployConfig.Complete(); err != nil {
-		return err
-	}
-
 	// config Complete
 	if err = c.cliOpts.Complete(); err != nil {
 		return err
 	}
 	c.client, err = kc.FromConfig(c.cliOpts.ToRawConfig())
+	if err != nil {
+		return err
+	}
 
-	return err
+	// deploy config Complete
+	c.deployConfig, err = deploy.GetDeployConfig(context.Background(), c.client, true)
+	if err != nil {
+		return errors.WithMessage(err, "get online deploy-config failed")
+	}
+	return nil
 }
 
 func (c *DrainOptions) ValidateArgs() error {
@@ -154,9 +158,7 @@ func (c *DrainOptions) ValidateArgs() error {
 	if c.cliOpts.Config == "" {
 		return errors.New("config path cannot be empty")
 	}
-	if c.deployConfig.Config == "" {
-		return errors.New("deploy config path cannot be empty")
-	}
+
 	if len(c.agents) == 0 {
 		return errors.New("--agent is required")
 	}
@@ -178,8 +180,10 @@ func (c *DrainOptions) preCheck() bool {
 
 	for _, agent := range c.agents {
 		if !c.deployConfig.Agents.ExistsByID(agent) {
-			logger.Errorf("agent %s is not in agent nodes", agent)
-			return false
+			_, _ = c.IOStreams.Out.Write([]byte(fmt.Sprintf("agent %s not in deploy config agent list, maybe input a wrong agent id or deploy config is not synced，still drain?  Please input (yes/no)", agent)))
+			if !utils.AskForConfirmation() {
+				return false
+			}
 		}
 	}
 
@@ -217,6 +221,10 @@ func (c *DrainOptions) runDrainAgentNode() error {
 			return err
 		}
 	}
+
+	if err := deploy.UpdateDeployConfig(context.Background(), c.client, c.deployConfig, true); err != nil {
+		logger.Warn("drain agent node success,but update online deploy-config failed, you can update manual,err:", err)
+	}
 	logger.Info("agent node drain completed. show command: 'kcctl get node'")
 	return nil
 }
@@ -259,10 +267,6 @@ func (c *DrainOptions) agentFilesAndData(node *v1.Node) error {
 
 	// 	3.rewrite deploy config
 	c.deployConfig.Agents.Delete(node.Name)
-	err = c.deployConfig.Write()
-	if err != nil {
-		return errors.WithMessage(err, "rewrite deploy config")
-	}
 	return nil
 }
 
diff --git a/pkg/cli/join/join.go b/pkg/cli/join/join.go
index 92632da..c96b1a8 100644
--- a/pkg/cli/join/join.go
+++ b/pkg/cli/join/join.go
@@ -20,6 +20,7 @@ package join
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"path"
 	"path/filepath"
@@ -28,7 +29,9 @@ import (
 	"github.com/google/uuid"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
-	"k8s.io/apimachinery/pkg/util/sets"
+
+	"github.com/kubeclipper/kubeclipper/pkg/cli/deploy"
+	"github.com/kubeclipper/kubeclipper/pkg/simple/client/kc"
 
 	"github.com/kubeclipper/kubeclipper/pkg/utils/autodetection"
 
@@ -45,18 +48,14 @@ const (
 	longDescription = `
   Add Server and Agents nodes on kubeclipper platform.
 
-  At least one Server node must be installed before adding an Agents node.
-  deploy-config.yaml file is used to check whether a node can be added correctly.`
+  At least one Server node must be installed before adding an Agents node.`
 	joinExample = `
-  # Add agent node use default config.
+  # Add agent node.
   kcctl join --agent 192.168.10.123
 
   # Add agent node specify region.
   kcctl join --agent us-west-1:192.168.10.123
 
-  # Add agent node specify config.
-  kcctl join --agent 192.168.10.123 --deploy-config ~/.kc/deploy-config.yaml
-
   # Add multiple agent nodes.
   kcctl join --agent 192.168.10.123,192.168.10.124
 
@@ -70,8 +69,8 @@ const (
   # this will add 10 agent,1.1.1.1, 1.1.1.2, ... 1.1.1.10.
   kcctl join --agent us-west-1:1.1.1.1-1.1.1.10
 
-  # Add multiple agent nodes and config fip.
-  kcctl join --agent 192.168.10.123,192.168.10.124 --fip 192.168.10.123:172.20.149.199 --fip 192.168.10.124:172.20.149.200
+  # Add multiple agent nodes and config float ip.
+  kcctl join --agent 192.168.10.123,192.168.10.124 --float-ip 192.168.10.123:172.20.149.199 --float-ip 192.168.10.124:172.20.149.200
 
   Please read 'kcctl join -h' get more deploy flags`
 )
@@ -79,16 +78,18 @@ const (
 type JoinOptions struct {
 	options.IOStreams
 	deployConfig *options.DeployConfig
+	cliOpts      *options.CliOptions
+	client       *kc.Client
 
 	agents     []string // user input agents,maybe with region,need to parse.
 	floatIPs   []string // format: ip:floatIP,e.g. 192.168.10.11:172.20.149.199
-	servers    []string
 	ipDetect   string
 	parseAgent options.Agents
 }
 
 func NewJoinOptions(streams options.IOStreams) *JoinOptions {
 	return &JoinOptions{
+		cliOpts:      options.NewCliOptions(),
 		IOStreams:    streams,
 		deployConfig: options.NewDeployOptions(),
 		ipDetect:     autodetection.MethodFirst,
@@ -113,16 +114,16 @@ func NewCmdJoin(streams options.IOStreams) *cobra.Command {
 			utils.CheckErr(o.RunJoinFunc())
 		},
 	}
+	o.cliOpts.AddFlags(cmd.Flags())
 	cmd.Flags().StringVar(&o.ipDetect, "ip-detect", o.ipDetect, "Kc ip detect method.")
 	cmd.Flags().StringArrayVar(&o.agents, "agent", o.agents, "join agent node.")
 	cmd.Flags().StringArrayVar(&o.floatIPs, "float-ip", o.floatIPs, "Kc agent ip and float ip.")
-	cmd.Flags().StringVar(&o.deployConfig.Config, "deploy-config", options.DefaultDeployConfigPath, "kcctl deploy config path")
 	utils.CheckErr(cmd.MarkFlagRequired("agent"))
 	return cmd
 }
 
 func (c *JoinOptions) preCheck() bool {
-	if !sudo.PreCheck("sudo", c.deployConfig.SSHConfig, c.IOStreams, append(c.parseAgent.ListIP(), c.servers...)) {
+	if !sudo.PreCheck("sudo", c.deployConfig.SSHConfig, c.IOStreams, append(c.parseAgent.ListIP(), c.deployConfig.ServerIPs...)) {
 		return false
 	}
 	// check if the node is already added
@@ -135,19 +136,27 @@ func (c *JoinOptions) preCheck() bool {
 }
 
 func (c *JoinOptions) Complete() error {
-	// deploy config Complete
-	if err := c.deployConfig.Complete(); err != nil {
+	var err error
+	if c.parseAgent, err = deploy.BuildAgent(c.agents, c.floatIPs, c.deployConfig.DefaultRegion); err != nil {
+		return err
+	}
+	// config Complete
+	if err = c.cliOpts.Complete(); err != nil {
 		return err
 	}
+	c.client, err = kc.FromConfig(c.cliOpts.ToRawConfig())
+	if err != nil {
+		return err
+	}
+	// deploy config Complete
+	c.deployConfig, err = deploy.GetDeployConfig(context.Background(), c.client, true)
+	if err != nil {
+		return errors.WithMessage(err, "get online deploy-config failed")
+	}
 	// overwrite by specify
 	if c.ipDetect != "" {
 		c.deployConfig.IPDetect = c.ipDetect
 	}
-	var err error
-	if c.parseAgent, err = BuildAgent(c.agents, c.floatIPs, c.deployConfig.DefaultRegion); err != nil {
-		return err
-	}
-	c.servers = sets.NewString(c.servers...).List()
 	return nil
 }
 
@@ -191,18 +200,20 @@ func (c *JoinOptions) RunJoinNode() error {
 }
 
 func (c *JoinOptions) runJoinAgentNode() error {
-	var err error
 	for ip := range c.parseAgent {
 		metadata := c.parseAgent[ip]
 		metadata.AgentID = uuid.New().String()
 		c.parseAgent[ip] = metadata
-		if err = c.agentNodeFiles(ip, metadata); err != nil {
+		if err := c.agentNodeFiles(ip, metadata); err != nil {
 			return err
 		}
-		if err = c.enableAgent(ip, metadata); err != nil {
+		if err := c.enableAgent(ip, metadata); err != nil {
 			return err
 		}
 	}
+	if err := deploy.UpdateDeployConfig(context.Background(), c.client, c.deployConfig, true); err != nil {
+		logger.Warn("drain agent node success,but update online deploy-config failed, you can update manual,err:", err)
+	}
 	logger.Info("agent node join completed. show command: 'kcctl get node'")
 	return nil
 }
@@ -270,9 +281,12 @@ func (c *JoinOptions) enableAgent(node string, metadata options.Metadata) error
 	if err = ret.Error(); err != nil {
 		return errors.Wrap(err, "enable kc agent")
 	}
-	// update deploy-config.yaml
+	// add deploy-config
+	if c.deployConfig.Agents == nil {
+		c.deployConfig.Agents = make(options.Agents)
+	}
 	c.deployConfig.Agents.Add(node, metadata)
-	return c.deployConfig.Write()
+	return nil
 }
 
 func (c *JoinOptions) runJoinServerNode() error {
diff --git a/pkg/cli/proxy/deploy.go b/pkg/cli/proxy/deploy.go
index 72da8c8..1fa33c8 100644
--- a/pkg/cli/proxy/deploy.go
+++ b/pkg/cli/proxy/deploy.go
@@ -19,16 +19,20 @@
 package proxy
 
 import (
+	"context"
 	"fmt"
 	"path"
 	"path/filepath"
 
+	"github.com/pkg/errors"
 	"gopkg.in/yaml.v2"
 
 	"github.com/kubeclipper/kubeclipper/pkg/utils/sliceutil"
 
 	"github.com/kubeclipper/kubeclipper/pkg/cli/config"
+	"github.com/kubeclipper/kubeclipper/pkg/cli/deploy"
 	proxyConfig "github.com/kubeclipper/kubeclipper/pkg/proxy/config"
+	"github.com/kubeclipper/kubeclipper/pkg/simple/client/kc"
 	"github.com/kubeclipper/kubeclipper/pkg/utils/sshutils"
 
 	"github.com/kubeclipper/kubeclipper/pkg/cli/logger"
@@ -47,21 +51,20 @@ const (
   kc-agent can access kc-server by proxy server,kc-server can access kc-agent by proxy server too.`
 
 	deployExample = `
-  # Deploy proxy use default deploy config(~/.kc/deploy-config.yaml)
-  # run proxy server on node 192.168.234.4, add 2 tunnels in 1 agent node.
-  kcctl proxy --proxy 192.168.234.4 --tunnels 16443:192.168.234.5:6443 --tunnels 10022:192.168.234.5:22
+  # Deploy proxy,run proxy server on node 192.168.234.4, add 2 tunnels in 1 agent node.
+  # this means proxy 192.168.234.4's 16443/10022 port to 192.168.234.5's 6443/22 port
+  kcctl proxy --pkg /tmp/kc.tar.gz --proxy 192.168.234.4 --tunnels 16443:192.168.234.5:6443 --tunnels 10022:192.168.234.5:22
 
-  # Deploy proxy specify deploy config path.
-  # run proxy server on node 192.168.234.4, add 4 tunnels in 2 agent node.
-  kcctl proxy --deploy-config /root/.kc/deploy-config --proxy 192.168.234.4 --tunnels 16443:192.168.234.5:6443 --tunnels 10022:192.168.234.5:22 --tunnels 16443:192.168.234.6:6443 --tunnels 10022:192.168.234.6:22`
+  # Deploy proxy,run proxy server on node 192.168.234.4, add 4 tunnels in 2 agent node.
+  kcctl proxy --pkg /tmp/kc.tar.gz --proxy 192.168.234.4 --tunnels 16443:192.168.234.5:6443 --tunnels 10022:192.168.234.5:22 --tunnels 26443:192.168.234.6:6443 --tunnels 20022:192.168.234.6:22`
 )
 
-// kcctl proxy --pkg kc-test.tar.gz --proxy 192.168.20.212 --tunnels 16443:192.168.20.235:6443 --tunnels 10022:192.168.20.235:22 --v 5
-
 type ProxyOptions struct {
 	options.IOStreams
 	deployConfig *options.DeployConfig
 	config       *proxyConfig.Config
+	cliOpts      *options.CliOptions
+	client       *kc.Client
 	pkg          string
 	proxy        string
 	tunnelStr    []string
@@ -70,6 +73,7 @@ type ProxyOptions struct {
 
 func NewProxyOptions(streams options.IOStreams) *ProxyOptions {
 	return &ProxyOptions{
+		cliOpts:      options.NewCliOptions(),
 		IOStreams:    streams,
 		deployConfig: options.NewDeployOptions(),
 	}
@@ -94,17 +98,16 @@ func NewCmdProxy(streams options.IOStreams) *cobra.Command {
 
 	cmd.Flags().StringArrayVar(&o.tunnelStr, "tunnels", o.tunnelStr, "proxy tunnels.")
 	cmd.Flags().StringVar(&o.proxy, "proxy", o.proxy, "kc proxy server")
-	cmd.Flags().StringVar(&o.deployConfig.Config, "deploy-config", options.DefaultDeployConfigPath, "kcctl deploy config path")
 	cmd.Flags().StringVar(&o.pkg, "pkg", o.deployConfig.Pkg, "kubeclipper package resource url (path or http url)")
 
+	utils.CheckErr(cmd.MarkFlagRequired("pkg"))
+	utils.CheckErr(cmd.MarkFlagRequired("proxy"))
+	utils.CheckErr(cmd.MarkFlagRequired("tunnels"))
 	return cmd
 }
 
 func (d *ProxyOptions) Complete() error {
 	var err error
-	if err = d.deployConfig.Complete(); err != nil {
-		return err
-	}
 	if d.pkg == "" {
 		d.pkg = d.deployConfig.Pkg
 	}
@@ -112,6 +115,18 @@ func (d *ProxyOptions) Complete() error {
 	if err != nil {
 		return err
 	}
+	if err = d.cliOpts.Complete(); err != nil {
+		return err
+	}
+	d.client, err = kc.FromConfig(d.cliOpts.ToRawConfig())
+	if err != nil {
+		return err
+	}
+	d.deployConfig, err = deploy.GetDeployConfig(context.Background(), d.client, true)
+	if err != nil {
+		return errors.WithMessage(err, "get online deploy-config failed")
+	}
+
 	d.config = d.generateProxyConfig()
 	return nil
 }
@@ -136,12 +151,12 @@ func (d *ProxyOptions) RunDeploy() error {
 	d.sendPackage()
 	d.deployProxy()
 	d.removeTempFile()
+
 	if !sliceutil.HasString(d.deployConfig.Proxys, d.proxy) {
 		d.deployConfig.Proxys = append(d.deployConfig.Proxys, d.proxy)
-		err := d.deployConfig.Write()
-		if err != nil {
-			return err
-		}
+	}
+	if err := deploy.UpdateDeployConfig(context.Background(), d.client, d.deployConfig, true); err != nil {
+		return errors.WithMessage(err, "update online deploy-config failed")
 	}
 	fmt.Printf("\033[1;40;36m%s\033[0m\n", options.Contact)
 	return nil
@@ -180,7 +195,6 @@ func (d *ProxyOptions) deployProxy() {
 	if err != nil {
 		logger.Fatalf("[deploy proxy] marshal proxy config failed due to %s", err.Error())
 	}
-	logger.Info("config data:", string(configData))
 
 	cmdList := []string{
 		sshutils.WrapEcho(config.KcProxyService, "/usr/lib/systemd/system/kc-proxy.service"),
diff --git a/pkg/cli/resource/resource.go b/pkg/cli/resource/resource.go
index 8ac09b4..1d0149d 100644
--- a/pkg/cli/resource/resource.go
+++ b/pkg/cli/resource/resource.go
@@ -19,12 +19,17 @@
 package resource
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
 
+	"github.com/pkg/errors"
+
+	"github.com/kubeclipper/kubeclipper/pkg/cli/deploy"
+
 	"github.com/kubeclipper/kubeclipper/pkg/scheme"
 
 	"github.com/kubeclipper/kubeclipper/pkg/utils/cmdutil"
@@ -64,8 +69,7 @@ const (
 	listLongDescription = `
   List offline resource packs
 
-  You can list, push, or delete offline resource packs.
-  The deploy-config flag is '/root/.kc/deploy-config.yaml' by defualt.`
+  You can list, push, or delete offline resource packs.`
 	resourceListExample = `
   # List offline resource
   kcctl resource list 
@@ -73,15 +77,11 @@ const (
   # List offline resource use specified output format
   kcctl resource list  --output 'YAML|TABLE|JSON'
 
-  # List offline resource use specified deploy file
-  kcctl resource list --deploy-config /root/.kc/deploy-config.yaml
-
   Please read 'kcctl resource list -h' get more resource list flags`
 	pushLongDescription = `
   Push offline resource packs
 
   You can push a .tar.gz file of the specified type
-  The deploy-config flag is '/root/.kc/deploy-config.yaml' by defualt.
 
   Naming rules for offline packages: name-version-arch.tar.gz
   Structure of the offline package: 
@@ -96,16 +96,15 @@ const (
   # Push docker offline resource csi
   kcctl resource push --pkg /root/docker-19.03.12-amd64.tar.gz --type cri
 
-  # Push offline resource packs nfs use specified deploy file
-  kcctl resource push --deploy-config /root/.kc/deploy-config.yaml  --pkg /root/nfs-v4.0.2-amd64.tar.gz --type csi
+  # Push offline resource nfs
+  kcctl resource push --pkg /root/nfs-v4.0.2-amd64.tar.gz --type csi
 
   Please read 'kcctl resource push -h' get more resource push flags`
 	deleteLongDescription = `
   Delete offline resource packs
 
   You can delete existing offline packages.
-  You need to specify the name, type, arch of offline packages before deleting.
-  The deploy-config flag is '/root/.kc/deploy-config.yaml' by defualt.`
+  You need to specify the name, type, arch of offline packages before deleting.`
 	resourceDeleteExample = `
   # Delete offline resource packs
   kcctl resource delete --name k8s --version v1.23.6 --arch amd64
@@ -116,8 +115,9 @@ const (
 type ResourceOptions struct {
 	options.IOStreams
 	PrintFlags   *printer.PrintFlags
-	DeployConfig string
 	deployConfig *options.DeployConfig
+	cliOpts      *options.CliOptions
+	client       *kc.Client
 
 	List   string
 	Push   string
@@ -133,6 +133,7 @@ type ResourceOptions struct {
 
 func NewResourceOptions(streams options.IOStreams) *ResourceOptions {
 	return &ResourceOptions{
+		cliOpts:      options.NewCliOptions(),
 		IOStreams:    streams,
 		PrintFlags:   printer.NewPrintFlags(),
 		deployConfig: options.NewDeployOptions(),
@@ -178,11 +179,11 @@ func NewCmdResourceList(o *ResourceOptions) *cobra.Command {
 	}
 
 	o.PrintFlags.AddFlags(cmd)
+	o.cliOpts.AddFlags(cmd.Flags())
 	cmd.Flags().StringVar(&o.Type, "type", o.Type, "offline resource type.")
 	cmd.Flags().StringVar(&o.Name, "name", o.Name, "offline resource name.")
 	cmd.Flags().StringVar(&o.Version, "version", o.Name, "offline resource version.")
 	cmd.Flags().StringVar(&o.Arch, "arch", o.Arch, "offline resource arch.")
-	cmd.Flags().StringVar(&o.DeployConfig, "deploy-config", options.DefaultDeployConfigPath, "kcctl deploy config path")
 
 	utils.CheckErr(cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return o.listType(toComplete), cobra.ShellCompDirectiveNoFileComp
@@ -222,9 +223,9 @@ func NewCmdResourcePush(o *ResourceOptions) *cobra.Command {
 		},
 	}
 
+	o.cliOpts.AddFlags(cmd.Flags())
 	cmd.Flags().StringVar(&o.Type, "type", o.Type, "offline resource type.")
 	cmd.Flags().StringVar(&o.Pkg, "pkg", o.Pkg, "docker service and images pkg.")
-	cmd.Flags().StringVar(&o.DeployConfig, "deploy-config", options.DefaultDeployConfigPath, "kcctl deploy config path")
 
 	utils.CheckErr(cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return o.listType(toComplete), cobra.ShellCompDirectiveNoFileComp
@@ -253,10 +254,10 @@ func NewCmdResourceDelete(o *ResourceOptions) *cobra.Command {
 		},
 	}
 
+	o.cliOpts.AddFlags(cmd.Flags())
 	cmd.Flags().StringVar(&o.Name, "name", o.Name, "offline resource name.")
 	cmd.Flags().StringVar(&o.Version, "version", o.Name, "offline resource version.")
 	cmd.Flags().StringVar(&o.Arch, "arch", o.Arch, "offline resource arch.")
-	cmd.Flags().StringVar(&o.DeployConfig, "deploy-config", options.DefaultDeployConfigPath, "kcctl deploy config path")
 
 	utils.CheckErr(cmd.RegisterFlagCompletionFunc("name", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		return o.listName(toComplete), cobra.ShellCompDirectiveNoFileComp
@@ -275,15 +276,23 @@ func NewCmdResourceDelete(o *ResourceOptions) *cobra.Command {
 }
 
 func (o *ResourceOptions) Complete() error {
-	o.deployConfig.Config = o.DeployConfig
-	err := o.deployConfig.Complete()
-	return err
+	var err error
+	if err = o.cliOpts.Complete(); err != nil {
+		return err
+	}
+	o.client, err = kc.FromConfig(o.cliOpts.ToRawConfig())
+	if err != nil {
+		return err
+	}
+
+	o.deployConfig, err = deploy.GetDeployConfig(context.Background(), o.client, true)
+	if err != nil {
+		return errors.WithMessage(err, "get online deploy-config failed")
+	}
+	return nil
 }
 
 func (o *ResourceOptions) ValidateArgs(cmd *cobra.Command) error {
-	if o.deployConfig.Config == "" {
-		return utils.UsageErrorf(cmd, "the deploy-config.yaml file path is required")
-	}
 	if o.deployConfig.SSHConfig.PkFile == "" && o.deployConfig.SSHConfig.Password == "" {
 		return fmt.Errorf("one of pkfile or password must be specify,please config it in %s", o.deployConfig.Config)
 	}
@@ -297,9 +306,6 @@ func (o *ResourceOptions) ValidateArgsPush(cmd *cobra.Command) error {
 	if o.Pkg == "" {
 		return utils.UsageErrorf(cmd, "resource pkg  must be specified")
 	}
-	if o.deployConfig.Config == "" {
-		return utils.UsageErrorf(cmd, "--deploy-config must be specified")
-	}
 	if o.deployConfig.SSHConfig.PkFile == "" && o.deployConfig.SSHConfig.Password == "" {
 		return fmt.Errorf("one of pkfile or password must be specify,please config it in %s", o.deployConfig.Config)
 	}
@@ -316,9 +322,6 @@ func (o *ResourceOptions) ValidateArgsDelete(cmd *cobra.Command) error {
 	if o.Arch == "" {
 		return utils.UsageErrorf(cmd, "the arch of resource must be specified")
 	}
-	if o.deployConfig.Config == "" {
-		return utils.UsageErrorf(cmd, "--deploy-config is required")
-	}
 	if o.deployConfig.SSHConfig.PkFile == "" && o.deployConfig.SSHConfig.Password == "" {
 		return fmt.Errorf("one of pkfile or password must be specify,please config it in %s", o.deployConfig.Config)
 	}
diff --git a/pkg/cli/upgrade/upgrade.go b/pkg/cli/upgrade/upgrade.go
index 994fd90..e030726 100644
--- a/pkg/cli/upgrade/upgrade.go
+++ b/pkg/cli/upgrade/upgrade.go
@@ -9,8 +9,11 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/pkg/errors"
 	"k8s.io/apimachinery/pkg/util/sets"
 
+	"github.com/kubeclipper/kubeclipper/pkg/cli/deploy"
+
 	"github.com/kubeclipper/kubeclipper/pkg/simple/client/kc"
 	"github.com/kubeclipper/kubeclipper/pkg/utils/cmdutil"
 
@@ -49,7 +52,7 @@ const (
   kcctl upgrade agent --pkg /tmp/kc-agent.tar.gz
 
   # Upgrade agent of kubeclipper platform use your own binary file
-  kcctl upgrade agent --pkg /tmp/kubeclipper-server --binary`
+  kcctl upgrade agent --pkg /tmp/kubeclipper-agent --binary`
 )
 
 var (
@@ -104,7 +107,6 @@ func NewCmdUpgrade(stream options.IOStreams) *cobra.Command {
 			utils.CheckErr(o.RunUpgrade())
 		},
 	}
-	cmd.Flags().StringVar(&o.deployConfig.Config, "deploy-config", options.DefaultDeployConfigPath, "deploy-config file path.")
 	cmd.Flags().StringVar(&o.pkg, "pkg", o.pkg, "Path to the package used for the upgrade")
 	cmd.Flags().BoolVar(&o.binary, "binary", o.binary, "Upgrade with the specified binary file")
 	cmd.Flags().BoolVar(&o.online, "online", o.online, "upgrade with online package")
@@ -114,20 +116,19 @@ func NewCmdUpgrade(stream options.IOStreams) *cobra.Command {
 }
 
 func (o *UpgradeOptions) Complete() error {
-	if err := o.deployConfig.Complete(); err != nil {
-		return nil
-	}
-	if o.deployConfig.ServerIPs == nil {
-		return fmt.Errorf("server node can't be empty, please check deploy-config file")
-	}
-	if err := o.CliOpts.Complete(); err != nil {
+	var err error
+	if err = o.CliOpts.Complete(); err != nil {
 		return err
 	}
-	c, err := kc.FromConfig(o.CliOpts.ToRawConfig())
+	o.client, err = kc.FromConfig(o.CliOpts.ToRawConfig())
 	if err != nil {
 		return err
 	}
-	o.client = c
+
+	o.deployConfig, err = deploy.GetDeployConfig(context.Background(), o.client, true)
+	if err != nil {
+		return errors.WithMessage(err, "get online deploy-config failed")
+	}
 	return nil
 }
 
diff --git a/pkg/controller/cluster_status.go b/pkg/controller/cluster_status.go
index facae5a..2f25664 100644
--- a/pkg/controller/cluster_status.go
+++ b/pkg/controller/cluster_status.go
@@ -25,9 +25,10 @@ import (
 	"strings"
 	"time"
 
-	"github.com/kubeclipper/kubeclipper/pkg/clustermanage/kubeadm"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
+	"github.com/kubeclipper/kubeclipper/pkg/clustermanage/kubeadm"
+
 	"github.com/kubeclipper/kubeclipper/pkg/clustermanage"
 	"github.com/kubeclipper/kubeclipper/pkg/scheme/common"
 
diff --git a/pkg/controller/clustercontroller/controller.go b/pkg/controller/clustercontroller/controller.go
index e201ace..38de3e9 100644
--- a/pkg/controller/clustercontroller/controller.go
+++ b/pkg/controller/clustercontroller/controller.go
@@ -24,6 +24,13 @@ import (
 	"strings"
 	"time"
 
+	"go.uber.org/zap"
+	"k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/clientcmd"
+
 	"github.com/kubeclipper/kubeclipper/pkg/client/informers"
 	listerv1 "github.com/kubeclipper/kubeclipper/pkg/client/lister/core/v1"
 	"github.com/kubeclipper/kubeclipper/pkg/clustermanage"
@@ -40,12 +47,6 @@ import (
 	"github.com/kubeclipper/kubeclipper/pkg/scheme/common"
 	v1 "github.com/kubeclipper/kubeclipper/pkg/scheme/core/v1"
 	"github.com/kubeclipper/kubeclipper/pkg/service"
-	"go.uber.org/zap"
-	"k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/tools/clientcmd"
 
 	ctrl "github.com/kubeclipper/kubeclipper/pkg/controller-runtime"
 	"github.com/kubeclipper/kubeclipper/pkg/logger"
diff --git a/pkg/simple/client/kc/api.go b/pkg/simple/client/kc/api.go
index f9cc2a0..1a84388 100644
--- a/pkg/simple/client/kc/api.go
+++ b/pkg/simple/client/kc/api.go
@@ -436,7 +436,7 @@ func (cli *Client) DeleteConfigMap(ctx context.Context, name string) error {
 }
 
 func (cli *Client) UpdateConfigMap(ctx context.Context, cm *v1.ConfigMap) (*ConfigMapList, error) {
-	serverResp, err := cli.put(ctx, fmt.Sprintf("%s/%s", configmapPath, cm.Name), nil, nil, nil)
+	serverResp, err := cli.put(ctx, fmt.Sprintf("%s/%s", configmapPath, cm.Name), nil, cm, nil)
 	defer ensureReaderClosed(serverResp)
 	if err != nil {
 		return nil, err
diff --git a/pkg/simple/client/kc/client.go b/pkg/simple/client/kc/client.go
index 90c3485..5645488 100644
--- a/pkg/simple/client/kc/client.go
+++ b/pkg/simple/client/kc/client.go
@@ -20,10 +20,13 @@ package kc
 
 import (
 	"context"
+	"errors"
 	"net/http"
 	"net/url"
+	"strings"
 
 	"github.com/kubeclipper/kubeclipper/pkg/cli/config"
+	"github.com/kubeclipper/kubeclipper/pkg/query"
 )
 
 const (
@@ -40,13 +43,25 @@ type Client struct {
 func FromConfig(c config.Config) (*Client, error) {
 	ctx := c.Contexts[c.CurrentContext]
 
-	return NewClientWithOpts(WithHost(c.Servers[ctx.Server].Server),
+	cli, err := NewClientWithOpts(WithHost(c.Servers[ctx.Server].Server),
 		WithScheme("http"),
 		WithBearerAuth(c.AuthInfos[ctx.AuthInfo].Token))
+	if err != nil {
+		return nil, err
+	}
+
+	// call api to  check is token valid
+	q := query.New()
+	if _, err = cli.ListConfigMaps(context.TODO(), Queries(*q)); err != nil {
+		if strings.Contains(err.Error(), "Unauthorized") {
+			return nil, errors.New("unauthorized,please use kcctl login cmd to login first")
+		}
+		return nil, err
+	}
+	return cli, nil
 }
 
 func NewClientWithOpts(opts ...Opt) (*Client, error) {
-
 	c := &Client{
 		client: http.DefaultClient,
 		scheme: defaultHTTPScheme,


Test patch:
diff --git a/pkg/cli/join/agent_test.go b/pkg/cli/deploy/agent_test.go
similarity index 99%
rename from pkg/cli/join/agent_test.go
rename to pkg/cli/deploy/agent_test.go
index 93f3bcb..f8ef3a4 100644
--- a/pkg/cli/join/agent_test.go
+++ b/pkg/cli/deploy/agent_test.go
@@ -16,7 +16,7 @@
  *
  */
 
-package join
+package deploy
 
 import (
 	"reflect"
diff --git a/pkg/cli/resource/resource_test.go b/pkg/cli/resource/resource_test.go
index ab832da..e69ea83 100644
--- a/pkg/cli/resource/resource_test.go
+++ b/pkg/cli/resource/resource_test.go
@@ -74,7 +74,6 @@ func TestResourceOptions_parsePackageName(t *testing.T) {
 			o := &ResourceOptions{
 				IOStreams:    tt.fields.IOStreams,
 				PrintFlags:   tt.fields.PrintFlags,
-				DeployConfig: tt.fields.DeployConfig,
 				deployConfig: tt.fields.deployConfig,
 				List:         tt.fields.List,
 				Push:         tt.fields.Push,