Unverified Commit fa478dd9 authored by Silvano Cerza's avatar Silvano Cerza Committed by GitHub

Fix gRPC interface function to merge configs (#1164)

* Fix gRPC interface function to merge configs

This fix adds the possibility to set empty values with the Merge gRPC
function, previously they would have been ignored.

Because of this change I had also to modify the GetValue() function
since it would first check if the value was set using the
Viper.InConfig() function that wouldn't check for values set with
Viper.Set().

* [skip changelog] Add clearer example to client_example

* [skip changelog] Simplified some code and enhance a test
parent b3fb43f4
...@@ -83,8 +83,20 @@ func main() { ...@@ -83,8 +83,20 @@ func main() {
callGetAll(settingsClient) callGetAll(settingsClient)
// Merge applies multiple settings values at once. // Merge applies multiple settings values at once.
log.Println("calling Merge(`{\"foo\": \"bar\", \"daemon\":{\"port\":\"422\"}}`)") log.Println("calling Merge()")
callMerge(settingsClient) callMerge(settingsClient, `{"foo": {"value": "bar"}, "daemon":{"port":"422"}, "board_manager": {"additional_urls":["https://example.com"]}}`)
log.Println("calling GetAll()")
callGetAll(settingsClient)
log.Println("calling Merge()")
callMerge(settingsClient, `{"foo": {} }`)
log.Println("calling GetAll()")
callGetAll(settingsClient)
log.Println("calling Merge()")
callMerge(settingsClient, `{"foo": "bar" }`)
// Get the value of the foo key. // Get the value of the foo key.
log.Println("calling GetValue(foo)") log.Println("calling GetValue(foo)")
...@@ -262,11 +274,10 @@ func callUnsetProxy(client settings.SettingsClient) { ...@@ -262,11 +274,10 @@ func callUnsetProxy(client settings.SettingsClient) {
} }
} }
func callMerge(client settings.SettingsClient) { func callMerge(client settings.SettingsClient, jsonData string) {
bulkSettings := `{"foo": "bar", "daemon":{"port":"422"}}`
_, err := client.Merge(context.Background(), _, err := client.Merge(context.Background(),
&settings.RawData{ &settings.RawData{
JsonData: bulkSettings, JsonData: jsonData,
}) })
if err != nil { if err != nil {
......
...@@ -19,6 +19,8 @@ import ( ...@@ -19,6 +19,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"strings"
"github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/configuration"
rpc "github.com/arduino/arduino-cli/rpc/settings" rpc "github.com/arduino/arduino-cli/rpc/settings"
...@@ -40,6 +42,31 @@ func (s *SettingsService) GetAll(ctx context.Context, req *rpc.GetAllRequest) (* ...@@ -40,6 +42,31 @@ func (s *SettingsService) GetAll(ctx context.Context, req *rpc.GetAllRequest) (*
return nil, err return nil, err
} }
// mapper converts a map of nested maps to a map of scalar values.
// For example:
// {"foo": "bar", "daemon":{"port":"420"}, "sketch": {"always_export_binaries": "true"}}
// would convert to:
// {"foo": "bar", "daemon.port":"420", "sketch.always_export_binaries": "true"}
func mapper(toMap map[string]interface{}) map[string]interface{} {
res := map[string]interface{}{}
for k, v := range toMap {
switch data := v.(type) {
case map[string]interface{}:
for mK, mV := range mapper(data) {
// Concatenate keys
res[fmt.Sprintf("%s.%s", k, mK)] = mV
}
// This is done to avoid skipping keys containing empty maps
if len(data) == 0 {
res[k] = map[string]interface{}{}
}
default:
res[k] = v
}
}
return res
}
// Merge applies multiple settings values at once. // Merge applies multiple settings values at once.
func (s *SettingsService) Merge(ctx context.Context, req *rpc.RawData) (*rpc.MergeResponse, error) { func (s *SettingsService) Merge(ctx context.Context, req *rpc.RawData) (*rpc.MergeResponse, error) {
var toMerge map[string]interface{} var toMerge map[string]interface{}
...@@ -47,8 +74,13 @@ func (s *SettingsService) Merge(ctx context.Context, req *rpc.RawData) (*rpc.Mer ...@@ -47,8 +74,13 @@ func (s *SettingsService) Merge(ctx context.Context, req *rpc.RawData) (*rpc.Mer
return nil, err return nil, err
} }
if err := configuration.Settings.MergeConfigMap(toMerge); err != nil { mapped := mapper(toMerge)
return nil, err
// Set each value individually.
// This is done because Viper ignores empty strings or maps when
// using the MergeConfigMap function.
for k, v := range mapped {
configuration.Settings.Set(k, v)
} }
return &rpc.MergeResponse{}, nil return &rpc.MergeResponse{}, nil
...@@ -61,7 +93,17 @@ func (s *SettingsService) GetValue(ctx context.Context, req *rpc.GetValueRequest ...@@ -61,7 +93,17 @@ func (s *SettingsService) GetValue(ctx context.Context, req *rpc.GetValueRequest
key := req.GetKey() key := req.GetKey()
value := &rpc.Value{} value := &rpc.Value{}
if !configuration.Settings.InConfig(key) { // Check if settings key actually existing, we don't use Viper.InConfig()
// since that doesn't check for keys formatted like daemon.port or those set
// with Viper.Set(). This way we check for all existing settings for sure.
keyExists := false
for _, k := range configuration.Settings.AllKeys() {
if k == key || strings.HasPrefix(k, key) {
keyExists = true
break
}
}
if !keyExists {
return nil, errors.New("key not found in settings") return nil, errors.New("key not found in settings")
} }
......
...@@ -48,12 +48,38 @@ func TestGetAll(t *testing.T) { ...@@ -48,12 +48,38 @@ func TestGetAll(t *testing.T) {
} }
func TestMerge(t *testing.T) { func TestMerge(t *testing.T) {
bulkSettings := `{"foo": "bar", "daemon":{"port":"420"}}` // Verify defaults
_, err := svc.Merge(context.Background(), &rpc.RawData{JsonData: bulkSettings}) require.Equal(t, "50051", configuration.Settings.GetString("daemon.port"))
require.Nil(t, err) require.Equal(t, "", configuration.Settings.GetString("foo"))
require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
bulkSettings := `{"foo": "bar", "daemon":{"port":"420"}, "sketch": {"always_export_binaries": "true"}}`
res, err := svc.Merge(context.Background(), &rpc.RawData{JsonData: bulkSettings})
require.NotNil(t, res)
require.NoError(t, err)
require.Equal(t, "420", configuration.Settings.GetString("daemon.port")) require.Equal(t, "420", configuration.Settings.GetString("daemon.port"))
require.Equal(t, "bar", configuration.Settings.GetString("foo")) require.Equal(t, "bar", configuration.Settings.GetString("foo"))
require.Equal(t, true, configuration.Settings.GetBool("sketch.always_export_binaries"))
bulkSettings = `{"foo":"", "daemon": {}, "sketch": {"always_export_binaries": "false"}}`
res, err = svc.Merge(context.Background(), &rpc.RawData{JsonData: bulkSettings})
require.NotNil(t, res)
require.NoError(t, err)
require.Equal(t, "50051", configuration.Settings.GetString("daemon.port"))
require.Equal(t, "", configuration.Settings.GetString("foo"))
require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
bulkSettings = `{"daemon": {"port":""}}`
res, err = svc.Merge(context.Background(), &rpc.RawData{JsonData: bulkSettings})
require.NotNil(t, res)
require.NoError(t, err)
require.Equal(t, "", configuration.Settings.GetString("daemon.port"))
// Verifies other values are not changed
require.Equal(t, "", configuration.Settings.GetString("foo"))
require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
reset() reset()
} }
...@@ -61,8 +87,32 @@ func TestMerge(t *testing.T) { ...@@ -61,8 +87,32 @@ func TestMerge(t *testing.T) {
func TestGetValue(t *testing.T) { func TestGetValue(t *testing.T) {
key := &rpc.GetValueRequest{Key: "daemon"} key := &rpc.GetValueRequest{Key: "daemon"}
resp, err := svc.GetValue(context.Background(), key) resp, err := svc.GetValue(context.Background(), key)
require.Nil(t, err) require.NoError(t, err)
require.Equal(t, `{"port":"50051"}`, resp.GetJsonData()) require.Equal(t, `{"port":"50051"}`, resp.GetJsonData())
key = &rpc.GetValueRequest{Key: "daemon.port"}
resp, err = svc.GetValue(context.Background(), key)
require.NoError(t, err)
require.Equal(t, `"50051"`, resp.GetJsonData())
}
func TestGetMergedValue(t *testing.T) {
// Verifies value is not set
key := &rpc.GetValueRequest{Key: "foo"}
res, err := svc.GetValue(context.Background(), key)
require.Nil(t, res)
require.Error(t, err, "Error getting settings value")
// Merge value
bulkSettings := `{"foo": "bar"}`
_, err = svc.Merge(context.Background(), &rpc.RawData{JsonData: bulkSettings})
require.NoError(t, err)
// Verifies value is correctly returned
key = &rpc.GetValueRequest{Key: "foo"}
res, err = svc.GetValue(context.Background(), key)
require.NoError(t, err)
require.Equal(t, `"bar"`, res.GetJsonData())
} }
func TestGetValueNotFound(t *testing.T) { func TestGetValueNotFound(t *testing.T) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment