Repository: bacalhau-project/bacalhau (language: python)

Issue:
allow = with config set
this work

```
→ bacalhau-1.5.0 config set api.port=2233
requires at least 2 arg(s), only received 1
```


Discussion / hints:

Fix patch:
diff --git a/cmd/cli/config/set.go b/cmd/cli/config/set.go
index 184588288e..d81b072e54 100644
--- a/cmd/cli/config/set.go
+++ b/cmd/cli/config/set.go
@@ -19,9 +19,27 @@ import (
 
 func newSetCmd() *cobra.Command {
 	setCmd := &cobra.Command{
-		Use:          "set",
-		Args:         cobra.MinimumNArgs(2),
-		Short:        "Set a value in the config.",
+		Use:   "set",
+		Args:  cobra.MinimumNArgs(1),
+		Short: "Set a value in the config.",
+		Long: `The 'set' command allows you to modify configuration values in your Bacalhau configuration file.
+
+This command supports two input formats for setting configuration values:
+
+1. Key-Value Pair Format:
+   You can provide the key and value as separate arguments.
+   Example:
+     bacalhau config set api.host 127.0.0.1
+     bacalhau config set compute.orchestrators http://127.0.0.1:1234 http://1.1.1.1:1234
+     bacalhau config set compute.labels foo=bar,baz=buz
+
+2. Key=Value Format:
+   Alternatively, you can pass the key and value together in a single argument using the 'key=value' format.
+   Example:
+     bacalhau config set api.host=127.0.0.1
+     bacalhau config set compute.orchestrators=http://127.0.0.1:1234,http://1.1.1.1:1234
+     bacalhau config set compute.labels=foo=bar,baz=buz
+`,
 		PreRunE:      hook.ClientPreRunHooks,
 		PostRunE:     hook.ClientPostRunHooks,
 		SilenceUsage: true,
@@ -43,7 +61,26 @@ func newSetCmd() *cobra.Command {
 				configPath = filepath.Join(bacalhauConfig.DataDir, config.DefaultFileName)
 			}
 
-			return setConfig(configPath, args[0], args[1:]...)
+			var (
+				key   string
+				value []string
+			)
+
+			// Check if the first argument is in key=value format
+			if strings.Contains(args[0], "=") {
+				parts := strings.SplitN(args[0], "=", 2)
+				key = parts[0]
+				value = []string{parts[1]}
+			} else {
+				// Fallback to the original behavior: key and value are separate arguments
+				if len(args) < 2 {
+					return fmt.Errorf("must provide both key and value, or key=value")
+				}
+				key = args[0]
+				value = args[1:]
+			}
+
+			return setConfig(configPath, key, value...)
 		},
 		// Provide auto completion for arguments to the `set` command
 		ValidArgsFunction: setAutoComplete,
diff --git a/pkg/config/types/parse.go b/pkg/config/types/parse.go
index 63707094d4..e3585243b7 100644
--- a/pkg/config/types/parse.go
+++ b/pkg/config/types/parse.go
@@ -68,6 +68,10 @@ func parseStringSlice(key string, values []string, typ reflect.Type) (any, error
 		return parseDuration(key, values[0])
 	}
 	if typ == reflect.TypeOf([]string{}) {
+		// NB: for the case `config set` is ued like `config set compute.orchestrators=123.456.789,987.654.321`
+		if len(values) == 1 && strings.Contains(values[0], ",") {
+			return strings.Split(values[0], ","), nil
+		}
 		return values, nil
 	}
 	return parseByKind(key, values[0], typ)


Test patch:
diff --git a/cmd/cli/config/set_test.go b/cmd/cli/config/set_test.go
index 96d6565ed8..9c1b73a650 100644
--- a/cmd/cli/config/set_test.go
+++ b/cmd/cli/config/set_test.go
@@ -50,7 +50,7 @@ func TestSetWithDefaultConfigPath(t *testing.T) {
 	require.Equal(t, expected, actual)
 }
 
-func TestSetMultipleValues(t *testing.T) {
+func TestSetMultipleSliceValues(t *testing.T) {
 	tempDir := t.TempDir()
 	os.Setenv("BACALHAU_DIR", tempDir)
 	defer os.Unsetenv("BACALHAU_DIR")
@@ -72,6 +72,28 @@ func TestSetMultipleValues(t *testing.T) {
 	require.Equal(t, expected, actual)
 }
 
+func TestSetMultipleMapValues(t *testing.T) {
+	tempDir := t.TempDir()
+	os.Setenv("BACALHAU_DIR", tempDir)
+	defer os.Unsetenv("BACALHAU_DIR")
+
+	cmd := cmd2.NewRootCmd()
+	cmd.SetArgs([]string{"config", "set", "compute.labels", "foo=bar,baz=buz"})
+
+	err := cmd.Execute()
+	require.NoError(t, err)
+
+	defaultConfigPath := filepath.Join(tempDir, "config.yaml")
+	actual := unmarshalConfigFile(t, defaultConfigPath)
+	expected := types.Bacalhau{Compute: types.Compute{
+		Labels: map[string]string{
+			"foo": "bar",
+			"baz": "buz",
+		},
+	}}
+	require.Equal(t, expected, actual)
+}
+
 func TestSetInvalidKey(t *testing.T) {
 	tempDir := t.TempDir()
 	t.Setenv("BACALHAU_DIR", tempDir)
@@ -112,6 +134,80 @@ func TestSetAdditiveChanges(t *testing.T) {
 	require.Equal(t, expected, actual)
 }
 
+func TestSetWithKeyValueFormat(t *testing.T) {
+	tempDir := t.TempDir()
+	t.Setenv("BACALHAU_DIR", tempDir)
+
+	cmd := cmd2.NewRootCmd()
+	// Using key=value format
+	cmd.SetArgs([]string{"config", "set", "api.host=1.1.1.1"})
+
+	err := cmd.Execute()
+	require.NoError(t, err)
+
+	defaultConfigPath := filepath.Join(tempDir, "config.yaml")
+	actual := unmarshalConfigFile(t, defaultConfigPath)
+	expected := types.Bacalhau{API: types.API{Host: "1.1.1.1"}}
+	require.Equal(t, expected, actual)
+}
+
+func TestSetMultipleSliceValuesKeyValueFormat(t *testing.T) {
+	tempDir := t.TempDir()
+	os.Setenv("BACALHAU_DIR", tempDir)
+	defer os.Unsetenv("BACALHAU_DIR")
+
+	cmd := cmd2.NewRootCmd()
+	cmd.SetArgs([]string{"config", "set", "compute.orchestrators=http://127.0.0.1:1234,http://1.1.1.1:1234"})
+
+	err := cmd.Execute()
+	require.NoError(t, err)
+
+	defaultConfigPath := filepath.Join(tempDir, "config.yaml")
+	actual := unmarshalConfigFile(t, defaultConfigPath)
+	expected := types.Bacalhau{Compute: types.Compute{
+		Orchestrators: []string{
+			"http://127.0.0.1:1234",
+			"http://1.1.1.1:1234",
+		},
+	}}
+	require.Equal(t, expected, actual)
+}
+
+func TestSetMultipleMapValuesKeyValueFormat(t *testing.T) {
+	tempDir := t.TempDir()
+	os.Setenv("BACALHAU_DIR", tempDir)
+	defer os.Unsetenv("BACALHAU_DIR")
+
+	cmd := cmd2.NewRootCmd()
+	cmd.SetArgs([]string{"config", "set", "compute.labels=foo=bar,baz=buz"})
+
+	err := cmd.Execute()
+	require.NoError(t, err)
+
+	defaultConfigPath := filepath.Join(tempDir, "config.yaml")
+	actual := unmarshalConfigFile(t, defaultConfigPath)
+	expected := types.Bacalhau{Compute: types.Compute{
+		Labels: map[string]string{
+			"foo": "bar",
+			"baz": "buz",
+		},
+	}}
+	require.Equal(t, expected, actual)
+}
+
+func TestSetInvalidKeyValueFormat(t *testing.T) {
+	tempDir := t.TempDir()
+	t.Setenv("BACALHAU_DIR", tempDir)
+
+	cmd := cmd2.NewRootCmd()
+	// Using key=value format but providing an invalid key
+	cmd.SetArgs([]string{"config", "set", "invalid.key=value"})
+
+	err := cmd.Execute()
+	require.Error(t, err)
+	require.Contains(t, err.Error(), "not a valid config key")
+}
+
 func unmarshalConfigFile(t testing.TB, path string) types.Bacalhau {
 	configFile, err := os.Open(path)
 	require.NoError(t, err)