* Create the config set command in the variable * Register the command and declare the command in the run function * Finish implementation of 'config set' cli command * Write tests for config set command * Change minimum number of arguments to 2 * Correct changes * Correct error problem * Update the command description and errors * Refactor function name and improve error messages * Write test for UpdateMap function
Этот коммит содержится в:
коммит произвёл
George Goldberg
родитель
937b6480d5
Коммит
2b6d6acb78
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -58,6 +59,15 @@ var ConfigShowCmd = &cobra.Command{
|
||||
RunE: configShowCmdF,
|
||||
}
|
||||
|
||||
var ConfigSetCmd = &cobra.Command{
|
||||
Use: "set",
|
||||
Short: "Set config setting",
|
||||
Long: "Sets the value of a config setting by its name in dot notation. Accepts multiple values for array settings",
|
||||
Example: "config set SqlSettings.DriverName mysql",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: configSetCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfigSubpathCmd.Flags().String("path", "", "Optional subpath; defaults to value in SiteURL")
|
||||
|
||||
@@ -66,6 +76,7 @@ func init() {
|
||||
ConfigSubpathCmd,
|
||||
ConfigGetCmd,
|
||||
ConfigShowCmd,
|
||||
ConfigSetCmd,
|
||||
)
|
||||
RootCmd.AddCommand(ConfigCmd)
|
||||
}
|
||||
@@ -224,6 +235,145 @@ func printMap(value reflect.Value, tabVal int) string {
|
||||
|
||||
}
|
||||
|
||||
func configSetCmdF(command *cobra.Command, args []string) error {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer app.Shutdown()
|
||||
|
||||
// args[0] -> holds the config setting that we want to change
|
||||
// args[1:] -> the new value of the config setting
|
||||
configSetting := args[0]
|
||||
newVal := args[1:]
|
||||
|
||||
// Update the config
|
||||
|
||||
// first disable the watchers
|
||||
app.DisableConfigWatch()
|
||||
|
||||
// create the function to update config
|
||||
oldConfig := app.Config()
|
||||
newConfig := app.Config()
|
||||
f := updateConfigValue(configSetting, newVal, oldConfig, newConfig)
|
||||
|
||||
// update the config
|
||||
app.UpdateConfig(f)
|
||||
|
||||
// make the changes persist
|
||||
app.PersistConfig()
|
||||
|
||||
// reload config
|
||||
app.ReloadConfig()
|
||||
|
||||
// Enable config watchers
|
||||
app.EnableConfigWatch()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateConfigValue(configSetting string, newVal []string, oldConfig, newConfig *model.Config) func(*model.Config) {
|
||||
return func(update *model.Config) {
|
||||
|
||||
// convert config to map[string]interface
|
||||
configMap := configToMap(*oldConfig)
|
||||
|
||||
// iterate through the map and update the value or print an error and exit
|
||||
err := UpdateMap(configMap, strings.Split(configSetting, "."), newVal)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// convert map to json
|
||||
bs, err := json.Marshal(configMap)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while marshalling map to json %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// convert json to struct
|
||||
err = json.Unmarshal(bs, newConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while unmarshalling json to struct %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
*update = *newConfig
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal []string) error {
|
||||
res, ok := configMap[configSettings[0]]
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(res)
|
||||
|
||||
switch value.Kind() {
|
||||
|
||||
case reflect.Map:
|
||||
// we can only change the value of a particular setting, not the whole map, return error
|
||||
if len(configSettings) == 1 {
|
||||
return errors.New("unable to set multiple settings at once")
|
||||
}
|
||||
return UpdateMap(res.(map[string]interface{}), configSettings[1:], newVal)
|
||||
|
||||
case reflect.Int:
|
||||
if len(configSettings) == 1 {
|
||||
val, err := strconv.Atoi(newVal[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap[configSettings[0]] = val
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
|
||||
|
||||
case reflect.Int64:
|
||||
if len(configSettings) == 1 {
|
||||
val, err := strconv.Atoi(newVal[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap[configSettings[0]] = int64(val)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
|
||||
|
||||
case reflect.Bool:
|
||||
if len(configSettings) == 1 {
|
||||
val, err := strconv.ParseBool(newVal[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap[configSettings[0]] = val
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
|
||||
|
||||
case reflect.String:
|
||||
if len(configSettings) == 1 {
|
||||
configMap[configSettings[0]] = newVal[0]
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
|
||||
|
||||
case reflect.Slice:
|
||||
if len(configSettings) == 1 {
|
||||
configMap[configSettings[0]] = newVal
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0])
|
||||
|
||||
default:
|
||||
return errors.New("type not supported yet")
|
||||
}
|
||||
}
|
||||
|
||||
// configToMap converts our config into a map
|
||||
func configToMap(s interface{}) map[string]interface{} {
|
||||
return structToMap(s)
|
||||
|
||||
@@ -54,6 +54,24 @@ type TestClientRequirements struct {
|
||||
Desktoplatestversion string
|
||||
}
|
||||
|
||||
type TestNewConfig struct {
|
||||
TestNewServiceSettings TestNewServiceSettings
|
||||
TestNewTeamSettings TestNewTeamSettings
|
||||
}
|
||||
|
||||
type TestNewServiceSettings struct{
|
||||
SiteUrl *string
|
||||
UseLetsEncrypt *bool
|
||||
TLSStrictTransportMaxAge *int64
|
||||
AllowedThemes []string
|
||||
}
|
||||
|
||||
|
||||
type TestNewTeamSettings struct {
|
||||
SiteName *string
|
||||
MaxUserPerTeam *int
|
||||
}
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
@@ -416,5 +434,122 @@ func TestConfigShow(t *testing.T) {
|
||||
assert.Contains(t, string(output), "SqlSettings")
|
||||
assert.Contains(t, string(output), "MessageExportSettings")
|
||||
assert.Contains(t, string(output), "AnnouncementSettings")
|
||||
}
|
||||
|
||||
func TestSetConfig(t *testing.T) {
|
||||
// Error when no argument is given
|
||||
assert.Error(t, RunCommand(t, "config", "set"))
|
||||
|
||||
// No Error when more than one argument is given
|
||||
assert.NoError(t, RunCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello", "World"))
|
||||
|
||||
// No Error when two arguments are given
|
||||
assert.NoError(t, RunCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello"))
|
||||
|
||||
// Error when only one argument is given
|
||||
assert.Error(t, RunCommand(t, "config", "set", "ThemeSettings.AllowedThemes"))
|
||||
|
||||
// Error when config settings not in the config file are given
|
||||
assert.Error(t, RunCommand(t, "config", "set", "Abc"))
|
||||
}
|
||||
|
||||
func TestUpdateMap(t *testing.T) {
|
||||
|
||||
// create a config to make changes
|
||||
config := TestNewConfig{
|
||||
TestNewServiceSettings{
|
||||
SiteUrl: model.NewString("abc.def"),
|
||||
UseLetsEncrypt: model.NewBool(false),
|
||||
TLSStrictTransportMaxAge: model.NewInt64(36),
|
||||
AllowedThemes: []string{"Hello", "World"},
|
||||
},
|
||||
TestNewTeamSettings{
|
||||
SiteName: model.NewString("def.ghi"),
|
||||
MaxUserPerTeam: model.NewInt(12),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
// create a map of type map[string]interface
|
||||
configMap := configToMap(config)
|
||||
|
||||
cases := []struct{
|
||||
Name string
|
||||
configSettings []string
|
||||
newVal []string
|
||||
expected interface{}
|
||||
} {
|
||||
{
|
||||
Name: "check for Map and string",
|
||||
configSettings: []string{"TestNewServiceSettings", "SiteUrl"},
|
||||
newVal: []string{"siteurl"},
|
||||
expected: "siteurl",
|
||||
},
|
||||
{
|
||||
Name: "check for Map and bool",
|
||||
configSettings: []string{"TestNewServiceSettings", "UseLetsEncrypt"},
|
||||
newVal: []string{"true"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
Name: "check for Map and int64",
|
||||
configSettings: []string{"TestNewServiceSettings", "TLSStrictTransportMaxAge"},
|
||||
newVal: []string{"56"},
|
||||
expected: int64(56),
|
||||
},
|
||||
{
|
||||
Name: "check for Map and string Slice",
|
||||
configSettings: []string{"TestNewServiceSettings", "AllowedThemes"},
|
||||
newVal: []string{"hello1", "world1"},
|
||||
expected: []string{"hello1", "world1"},
|
||||
},
|
||||
{
|
||||
Name: "Map and string",
|
||||
configSettings: []string{"TestNewTeamSettings", "SiteName"},
|
||||
newVal: []string{"jkl.mno"},
|
||||
expected: "jkl.mno",
|
||||
},
|
||||
{
|
||||
Name: "Map and int",
|
||||
configSettings: []string{"TestNewTeamSettings", "MaxUserPerTeam"},
|
||||
newVal: []string{"18"},
|
||||
expected: 18,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, test := range cases {
|
||||
|
||||
t.Run(test.Name, func(t *testing.T){
|
||||
err := UpdateMap(configMap, test.configSettings, test.newVal)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal("Wasn't expecting an error: ", err)
|
||||
}
|
||||
|
||||
if !contains(configMap, test.expected, test.configSettings) {
|
||||
t.Error("update didn't happen")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func contains(configMap map[string]interface{}, v interface{}, configSettings []string) bool {
|
||||
|
||||
res := configMap[configSettings[0]]
|
||||
|
||||
value := reflect.ValueOf(res)
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Map:
|
||||
return contains(res.(map[string]interface{}), v, configSettings[1:])
|
||||
case reflect.Slice:
|
||||
return reflect.DeepEqual(value.Interface(), v)
|
||||
case reflect.Int64:
|
||||
return value.Interface() == v.(int64)
|
||||
default:
|
||||
return value.Interface() == v
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user