From 573708edf7fa2f1c1e1c292b351d7cf1113e3c6d Mon Sep 17 00:00:00 2001
From: jianxiaoguo <lijianguo1991@outlook.com>
Date: Thu, 25 Sep 2025 16:30:12 +0800
Subject: [PATCH] chore(workflow-cli): config push add merge param

---
 go.mod                                               |  2 +-
 go.sum                                               |  4 ++--
 internal/commands/commands.go                        |  4 ++--
 internal/commands/config.go                          | 12 ++++++------
 internal/commands/config_test.go                     |  2 +-
 internal/commands/healthchecks.go                    |  4 ++--
 internal/commands/limits.go                          |  4 ++--
 internal/commands/registry.go                        |  4 ++--
 internal/commands/tags.go                            |  4 ++--
 internal/commands/timeouts.go                        |  4 ++--
 internal/parser/config.go                            |  6 ++++--
 internal/parser/healthchecks.go                      |  1 +
 pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po |  4 ++++
 13 files changed, 31 insertions(+), 24 deletions(-)

diff --git a/go.mod b/go.mod
index d3a64121..4c4c5ead 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.25
 require (
 	github.com/chai2010/gettext-go v1.0.3
 	github.com/containerd/console v1.0.4
-	github.com/drycc/controller-sdk-go v0.0.0-20250917070349-910603c54927
+	github.com/drycc/controller-sdk-go v0.0.0-20250925082016-6b35f24ddb99
 	github.com/drycc/pkg v0.0.0-20250917064731-345368da3dbf
 	github.com/minio/selfupdate v0.6.0
 	github.com/olekukonko/tablewriter v0.0.5
diff --git a/go.sum b/go.sum
index 1bd67dc1..d5ecba12 100644
--- a/go.sum
+++ b/go.sum
@@ -11,8 +11,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/drycc/controller-sdk-go v0.0.0-20250917070349-910603c54927 h1:c1n5N5urQztpcBy93U+oBEWyRXXaa0k3PwCg9pzXqe0=
-github.com/drycc/controller-sdk-go v0.0.0-20250917070349-910603c54927/go.mod h1:eHcmYwg81ASlP55/U587xnBZnZoeZnPHXGeQ8nYWnsg=
+github.com/drycc/controller-sdk-go v0.0.0-20250925082016-6b35f24ddb99 h1:rl63cNXjiTKi/gPbk7Gw0KL0sKJe64w7a9ol/J1DAS0=
+github.com/drycc/controller-sdk-go v0.0.0-20250925082016-6b35f24ddb99/go.mod h1:eHcmYwg81ASlP55/U587xnBZnZoeZnPHXGeQ8nYWnsg=
 github.com/drycc/pkg v0.0.0-20250917064731-345368da3dbf h1:CYy3NoPhfFhkGAbEppTOQfY/HC2s0FJDcBgbtRKeweg=
 github.com/drycc/pkg v0.0.0-20250917064731-345368da3dbf/go.mod h1:BrrNrNskHKm+nJYhXfGuI114w8nupi0AMo8QZHID7CM=
 github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
diff --git a/internal/commands/commands.go b/internal/commands/commands.go
index 4d5b42bc..9189f153 100644
--- a/internal/commands/commands.go
+++ b/internal/commands/commands.go
@@ -46,10 +46,10 @@ type Commander interface {
 	CertAttach(string, string, string) error
 	CertDetach(string, string, string) error
 	ConfigInfo(string, string, string, int) error
-	ConfigSet(string, string, string, []string, string) error
+	ConfigSet(string, string, string, []string, bool, string) error
 	ConfigUnset(string, string, string, []string, string) error
 	ConfigPull(string, string, string, string, bool, bool) error
-	ConfigPush(string, string, string, string, string) error
+	ConfigPush(string, string, string, string, bool, string) error
 	ConfigAttach(string, string, string) error
 	ConfigDetach(string, string, string) error
 	DomainsList(string, int) error
diff --git a/internal/commands/config.go b/internal/commands/config.go
index 7f12138c..bc892b21 100644
--- a/internal/commands/config.go
+++ b/internal/commands/config.go
@@ -116,7 +116,7 @@ func (d *DryccCmd) ConfigInfo(appID string, ptype string, group string, version
 }
 
 // ConfigSet sets an app's config variables.
-func (d *DryccCmd) ConfigSet(appID string, ptype string, group string, configVars []string, confirm string) error {
+func (d *DryccCmd) ConfigSet(appID string, ptype string, group string, configVars []string, merge bool, confirm string) error {
 	appID, s, err := loader.LoadAppSettings(d.ConfigFile, appID)
 	if err != nil {
 		return err
@@ -138,7 +138,7 @@ func (d *DryccCmd) ConfigSet(appID string, ptype string, group string, configVar
 
 	quit := progress(d.WOut)
 	configObj := api.Config{Values: configMap}
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, merge)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
@@ -180,7 +180,7 @@ func (d *DryccCmd) ConfigUnset(appID string, ptype string, group string, configV
 	}
 
 	configObj := api.Config{Values: valuesMaps}
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 
 	quit <- true
 	<-quit
@@ -263,7 +263,7 @@ func (d *DryccCmd) ConfigPull(appID, ptype, group, fileName string, interactive
 }
 
 // ConfigPush pushes an app's config from a file.
-func (d *DryccCmd) ConfigPush(appID, ptype string, group string, fileName string, confirm string) error {
+func (d *DryccCmd) ConfigPush(appID, ptype string, group string, fileName string, merge bool, confirm string) error {
 	stat, err := os.Stdin.Stat()
 	if err != nil {
 		return err
@@ -309,7 +309,7 @@ func (d *DryccCmd) ConfigPush(appID, ptype string, group string, fileName string
 		}
 	}
 
-	return d.ConfigSet(appID, ptype, group, config, "yes")
+	return d.ConfigSet(appID, ptype, group, config, merge, "yes")
 }
 
 // ConfigAttach attaches config groups to a process type.
@@ -327,7 +327,7 @@ func (d *DryccCmd) ConfigAttach(appID string, ptype string, groups string) error
 		ptype: gs,
 	}
 	configObj := api.Config{ValuesRefs: refs}
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 
 	quit <- true
 	<-quit
diff --git a/internal/commands/config_test.go b/internal/commands/config_test.go
index e496d3bd..46e6614e 100644
--- a/internal/commands/config_test.go
+++ b/internal/commands/config_test.go
@@ -139,7 +139,7 @@ func TestConfigSet(t *testing.T) {
 	var b bytes.Buffer
 	cmdr := DryccCmd{WOut: &b, ConfigFile: cf}
 
-	err = cmdr.ConfigSet("foo", "web", "", []string{"TRUE=false", "DEBUG=true"}, "yes")
+	err = cmdr.ConfigSet("foo", "web", "", []string{"TRUE=false", "DEBUG=true"}, true, "yes")
 	assert.NoError(t, err)
 
 	assert.Equal(t, testutil.StripProgress(b.String()), `Creating config... done
diff --git a/internal/commands/healthchecks.go b/internal/commands/healthchecks.go
index 425adbb4..dc689614 100644
--- a/internal/commands/healthchecks.go
+++ b/internal/commands/healthchecks.go
@@ -120,7 +120,7 @@ func (d *DryccCmd) HealthchecksSet(appID, healthcheckType, ptype string, probe *
 	configObj := api.Config{Healthcheck: make(map[string]*api.Healthchecks)}
 	configObj.Healthcheck[ptype] = &healthcheckMap
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 
 	quit <- true
 	<-quit
@@ -157,7 +157,7 @@ func (d *DryccCmd) HealthchecksUnset(appID, ptype string, healthchecks []string)
 
 	configObj.Healthcheck = healthchecksMap
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 
 	quit <- true
 	<-quit
diff --git a/internal/commands/limits.go b/internal/commands/limits.go
index e021de6c..29b32f78 100644
--- a/internal/commands/limits.go
+++ b/internal/commands/limits.go
@@ -75,7 +75,7 @@ func (d *DryccCmd) LimitsSet(appID string, limits []string) error {
 
 	quit := progress(d.WOut)
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
@@ -107,7 +107,7 @@ func (d *DryccCmd) LimitsUnset(appID string, limits []string) error {
 		configObj.Limits = limitsMap
 	}
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
diff --git a/internal/commands/registry.go b/internal/commands/registry.go
index a509c535..831b231d 100644
--- a/internal/commands/registry.go
+++ b/internal/commands/registry.go
@@ -67,7 +67,7 @@ func (d *DryccCmd) RegistrySet(appID, ptype, username, password string) error {
 	}
 	configObj.Registry = registry
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
@@ -97,7 +97,7 @@ func (d *DryccCmd) RegistryUnset(appID, ptype string) error {
 		"password": nil,
 	}
 	configObj.Registry = registry
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
diff --git a/internal/commands/tags.go b/internal/commands/tags.go
index 0157368c..e0a009b2 100644
--- a/internal/commands/tags.go
+++ b/internal/commands/tags.go
@@ -69,7 +69,7 @@ func (d *DryccCmd) TagsSet(appID, ptype string, tags []string) error {
 	configObj := api.Config{Tags: make(map[string]api.ConfigTags)}
 	configObj.Tags[ptype] = tagsMap
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
@@ -99,7 +99,7 @@ func (d *DryccCmd) TagsUnset(appID, ptype string, tags []string) error {
 	}
 	configObj.Tags[ptype] = configTags
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
diff --git a/internal/commands/timeouts.go b/internal/commands/timeouts.go
index ecbde851..9fb9b81d 100644
--- a/internal/commands/timeouts.go
+++ b/internal/commands/timeouts.go
@@ -56,7 +56,7 @@ func (d *DryccCmd) TimeoutsSet(appID string, timeouts []string) error {
 
 	configObj.Timeout = timeoutsMap
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
@@ -89,7 +89,7 @@ func (d *DryccCmd) TimeoutsUnset(appID string, timeouts []string) error {
 
 	configObj.Timeout = valuesMap
 
-	_, err = config.Set(s.Client, appID, configObj)
+	_, err = config.Set(s.Client, appID, configObj, true)
 	quit <- true
 	<-quit
 	if d.checkAPICompatibility(s.Client, err) != nil {
diff --git a/internal/parser/config.go b/internal/parser/config.go
index 0da44bd8..6ad64553 100644
--- a/internal/parser/config.go
+++ b/internal/parser/config.go
@@ -85,7 +85,7 @@ func configSetCommand(cmdr *commands.DryccCmd) *cobra.Command {
 		Short: i18n.T("Set environment variables for an app"),
 		Long:  i18n.T("Sets environment variables for an application or config group"),
 		RunE: func(_ *cobra.Command, args []string) error {
-			return cmdr.ConfigSet(app, configFlags.ptype, configFlags.group, args, flags.confirm)
+			return cmdr.ConfigSet(app, configFlags.ptype, configFlags.group, args, true, flags.confirm)
 		},
 	}
 
@@ -174,6 +174,7 @@ read by foreman to load the local environment for your app.`),
 func configPushCommand(cmdr *commands.DryccCmd) *cobra.Command {
 	var flags struct {
 		path    string
+		merge   bool
 		confirm string
 	}
 
@@ -186,13 +187,14 @@ This file can be read by foreman
 to load the local environment for your app. The file should be piped via
 stdin, 'drycc config push < .env', or using the --path option.`),
 		RunE: func(_ *cobra.Command, _ []string) error {
-			return cmdr.ConfigPush(app, configFlags.ptype, configFlags.group, flags.path, flags.confirm)
+			return cmdr.ConfigPush(app, configFlags.ptype, configFlags.group, flags.path, flags.merge, flags.confirm)
 		},
 	}
 
 	cmd.Flags().StringVarP(&configFlags.ptype, "ptype", "p", "", i18n.T("The ptype for which the config needs to be push"))
 	cmd.Flags().StringVarP(&configFlags.group, "group", "g", "", i18n.T("The group for which the config needs to be push"))
 	cmd.Flags().StringVar(&flags.path, "path", ".env", i18n.T("A path leading to an environment file"))
+	cmd.Flags().BoolVarP(&flags.merge, "merge", "", false, i18n.T("Merge config values"))
 	cmd.Flags().StringVar(&flags.confirm, "confirm", "", i18n.T("To proceed, type 'yes'"))
 	cmd.Flags().SortFlags = false
 	cmd.MarkFlagsMutuallyExclusive("ptype", "group")
diff --git a/internal/parser/healthchecks.go b/internal/parser/healthchecks.go
index f03e3ff1..2e0185ec 100644
--- a/internal/parser/healthchecks.go
+++ b/internal/parser/healthchecks.go
@@ -178,6 +178,7 @@ port number to perform the socket connection on the Container.
 	}
 
 	cmd.Flags().StringVarP(&healthchecksFlags.ptype, "ptype", "p", "", i18n.T("The ptype for which the health check needs to be applied"))
+	cmd.MarkFlagRequired("ptype")
 	ptypeCompletion := completion.PtsCompletion{ArgsLen: -1, ConfigFile: &cmdr.ConfigFile, AppID: &app}
 	cmd.RegisterFlagCompletionFunc("ptype", ptypeCompletion.CompletionFunc)
 
diff --git a/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po b/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
index 9f89f224..3d5bb985 100644
--- a/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
+++ b/pkg/i18n/translations/drycc/zh_CN/LC_MESSAGES/cli.po
@@ -29,6 +29,10 @@ msgstr "用于推送应用程序代码的SSH公钥的本地文件路径"
 msgid "A path leading to an environment file"
 msgstr "指向环境文件的路径"
 
+#: internal/parser/config.go:179
+msgid "Merge config values"
+msgstr "合并配置"
+
 #: internal/parser/tls.go:121
 msgid "ACME account email"
 msgstr "ACME账户邮箱"
