From a3aa941b68fb4eb1347eeafd5fea69dac1852957 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Tue, 27 Aug 2024 22:02:21 -0700 Subject: [PATCH] implement wsh setconfig (#284) --- cmd/wsh/cmd/wshcmd-setconfig.go | 37 +++++++++++++++++++++ frontend/app/store/wshserver.ts | 53 +++++++++++++++++-------------- pkg/gogen/gogen.go | 6 ++-- pkg/tsgen/tsgen.go | 26 +++++++-------- pkg/util/utilfn/utilfn.go | 13 ++++++++ pkg/waveobj/waveobj.go | 7 ++-- pkg/wconfig/settingsconfig.go | 31 ++++++++++-------- pkg/wshrpc/wshclient/wshclient.go | 6 ++++ pkg/wshrpc/wshrpctypes.go | 1 + pkg/wshrpc/wshserver/wshserver.go | 5 +++ 10 files changed, 126 insertions(+), 59 deletions(-) create mode 100644 cmd/wsh/cmd/wshcmd-setconfig.go diff --git a/cmd/wsh/cmd/wshcmd-setconfig.go b/cmd/wsh/cmd/wshcmd-setconfig.go new file mode 100644 index 00000000..0a0cb111 --- /dev/null +++ b/cmd/wsh/cmd/wshcmd-setconfig.go @@ -0,0 +1,37 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/wavetermdev/thenextwave/pkg/wshrpc" + "github.com/wavetermdev/thenextwave/pkg/wshrpc/wshclient" +) + +var setConfigCmd = &cobra.Command{ + Use: "setconfig", + Short: "set config", + Args: cobra.MinimumNArgs(1), + Run: setConfigRun, + PreRunE: preRunSetupRpcClient, +} + +func init() { + rootCmd.AddCommand(setConfigCmd) +} + +func setConfigRun(cmd *cobra.Command, args []string) { + metaSetsStrs := args[:] + meta, err := parseMetaSets(metaSetsStrs) + if err != nil { + WriteStderr("[error] %v\n", err) + return + } + err = wshclient.SetConfigCommand(RpcClient, meta, &wshrpc.RpcOpts{Timeout: 2000}) + if err != nil { + WriteStderr("[error] setting config: %v\n", err) + return + } + WriteStdout("config set\n") +} diff --git a/frontend/app/store/wshserver.ts b/frontend/app/store/wshserver.ts index cc74e3b6..d98cc441 100644 --- a/frontend/app/store/wshserver.ts +++ b/frontend/app/store/wshserver.ts @@ -8,97 +8,97 @@ import * as WOS from "./wos"; // WshServerCommandToDeclMap class WshServerType { // command "announce" [call] - AnnounceCommand(data: string, opts?: RpcOpts): Promise { + AnnounceCommand(data: string, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("announce", data, opts); } // command "authenticate" [call] - AuthenticateCommand(data: string, opts?: RpcOpts): Promise { + AuthenticateCommand(data: string, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("authenticate", data, opts); } // command "controllerinput" [call] - ControllerInputCommand(data: CommandBlockInputData, opts?: RpcOpts): Promise { + ControllerInputCommand(data: CommandBlockInputData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("controllerinput", data, opts); } // command "controllerrestart" [call] - ControllerRestartCommand(data: CommandBlockRestartData, opts?: RpcOpts): Promise { + ControllerRestartCommand(data: CommandBlockRestartData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("controllerrestart", data, opts); } // command "createblock" [call] - CreateBlockCommand(data: CommandCreateBlockData, opts?: RpcOpts): Promise { + CreateBlockCommand(data: CommandCreateBlockData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("createblock", data, opts); } // command "deleteblock" [call] - DeleteBlockCommand(data: CommandDeleteBlockData, opts?: RpcOpts): Promise { + DeleteBlockCommand(data: CommandDeleteBlockData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("deleteblock", data, opts); } // command "eventpublish" [call] - EventPublishCommand(data: WaveEvent, opts?: RpcOpts): Promise { + EventPublishCommand(data: WaveEvent, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("eventpublish", data, opts); } // command "eventrecv" [call] - EventRecvCommand(data: WaveEvent, opts?: RpcOpts): Promise { + EventRecvCommand(data: WaveEvent, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("eventrecv", data, opts); } // command "eventsub" [call] - EventSubCommand(data: SubscriptionRequest, opts?: RpcOpts): Promise { + EventSubCommand(data: SubscriptionRequest, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("eventsub", data, opts); } // command "eventunsub" [call] - EventUnsubCommand(data: string, opts?: RpcOpts): Promise { + EventUnsubCommand(data: string, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("eventunsub", data, opts); } // command "eventunsuball" [call] - EventUnsubAllCommand(opts?: RpcOpts): Promise { + EventUnsubAllCommand(opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("eventunsuball", null, opts); } // command "fileappend" [call] - FileAppendCommand(data: CommandFileData, opts?: RpcOpts): Promise { + FileAppendCommand(data: CommandFileData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("fileappend", data, opts); } // command "fileappendijson" [call] - FileAppendIJsonCommand(data: CommandAppendIJsonData, opts?: RpcOpts): Promise { + FileAppendIJsonCommand(data: CommandAppendIJsonData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("fileappendijson", data, opts); } // command "fileread" [call] - FileReadCommand(data: CommandFileData, opts?: RpcOpts): Promise { + FileReadCommand(data: CommandFileData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("fileread", data, opts); } // command "filewrite" [call] - FileWriteCommand(data: CommandFileData, opts?: RpcOpts): Promise { + FileWriteCommand(data: CommandFileData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("filewrite", data, opts); } // command "getmeta" [call] - GetMetaCommand(data: CommandGetMetaData, opts?: RpcOpts): Promise { + GetMetaCommand(data: CommandGetMetaData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("getmeta", data, opts); } // command "message" [call] - MessageCommand(data: CommandMessageData, opts?: RpcOpts): Promise { + MessageCommand(data: CommandMessageData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("message", data, opts); } // command "remotefiledelete" [call] - RemoteFileDeleteCommand(data: string, opts?: RpcOpts): Promise { + RemoteFileDeleteCommand(data: string, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("remotefiledelete", data, opts); } // command "remotefileinfo" [call] - RemoteFileInfoCommand(data: string, opts?: RpcOpts): Promise { + RemoteFileInfoCommand(data: string, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("remotefileinfo", data, opts); } @@ -113,22 +113,27 @@ class WshServerType { } // command "remotewritefile" [call] - RemoteWriteFileCommand(data: CommandRemoteWriteFileData, opts?: RpcOpts): Promise { + RemoteWriteFileCommand(data: CommandRemoteWriteFileData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("remotewritefile", data, opts); } // command "resolveids" [call] - ResolveIdsCommand(data: CommandResolveIdsData, opts?: RpcOpts): Promise { + ResolveIdsCommand(data: CommandResolveIdsData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("resolveids", data, opts); } + // command "setconfig" [call] + SetConfigCommand(data: MetaMapType, opts?: RpcOpts): Promise { + return WOS.wshServerRpcHelper_call("setconfig", data, opts); + } + // command "setmeta" [call] - SetMetaCommand(data: CommandSetMetaData, opts?: RpcOpts): Promise { + SetMetaCommand(data: CommandSetMetaData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("setmeta", data, opts); } // command "setview" [call] - SetViewCommand(data: CommandBlockSetViewData, opts?: RpcOpts): Promise { + SetViewCommand(data: CommandBlockSetViewData, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("setview", data, opts); } @@ -148,7 +153,7 @@ class WshServerType { } // command "test" [call] - TestCommand(data: string, opts?: RpcOpts): Promise { + TestCommand(data: string, opts?: RpcOpts): Promise { return WOS.wshServerRpcHelper_call("test", data, opts); } diff --git a/pkg/gogen/gogen.go b/pkg/gogen/gogen.go index 99f45c96..eff2f83d 100644 --- a/pkg/gogen/gogen.go +++ b/pkg/gogen/gogen.go @@ -8,6 +8,7 @@ import ( "reflect" "strings" + "github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/wshrpc" ) @@ -42,10 +43,7 @@ func GenerateMetaMapConsts(buf *strings.Builder, constPrefix string, rtype refle continue } fieldName := field.Name - jsonTag := field.Tag.Get("json") - if commaIdx := strings.Index(jsonTag, ","); commaIdx != -1 { - jsonTag = jsonTag[:commaIdx] - } + jsonTag := utilfn.GetJsonTag(field) if jsonTag == "" { jsonTag = fieldName } diff --git a/pkg/tsgen/tsgen.go b/pkg/tsgen/tsgen.go index a8cf05e5..c9efc204 100644 --- a/pkg/tsgen/tsgen.go +++ b/pkg/tsgen/tsgen.go @@ -15,6 +15,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/service" "github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta" "github.com/wavetermdev/thenextwave/pkg/userinput" + "github.com/wavetermdev/thenextwave/pkg/util/utilfn" "github.com/wavetermdev/thenextwave/pkg/vdom" "github.com/wavetermdev/thenextwave/pkg/waveobj" "github.com/wavetermdev/thenextwave/pkg/wconfig" @@ -84,20 +85,15 @@ func getTSFieldName(field reflect.StructField) string { } return tsFieldTag } - jsonTag := field.Tag.Get("json") + jsonTag := utilfn.GetJsonTag(field) + if jsonTag == "-" { + return "" + } + if strings.Contains(jsonTag, ":") { + return "\"" + jsonTag + "\"" + } if jsonTag != "" { - parts := strings.Split(jsonTag, ",") - namePart := parts[0] - if namePart != "" { - if namePart == "-" { - return "" - } - if strings.Contains(namePart, ":") { - return "\"" + namePart + "\"" - } - return namePart - } - // if namePart is empty, still uses default + return jsonTag } return field.Name } @@ -452,9 +448,9 @@ func GenerateWshServerMethod_Call(methodDecl *wshrpc.WshRpcMethodDecl, tsTypesMa dataName = "data" } if methodDecl.CommandDataType != nil { - sb.WriteString(fmt.Sprintf(" %s(data: %s, opts?: RpcOpts): %s {\n", methodDecl.MethodName, methodDecl.CommandDataType.Name(), rtnType)) + sb.WriteString(fmt.Sprintf(" %s(data: %s, opts?: RpcOpts): %s {\n", methodDecl.MethodName, methodDecl.CommandDataType.Name(), rtnType)) } else { - sb.WriteString(fmt.Sprintf(" %s(opts?: RpcOpts): %s {\n", methodDecl.MethodName, rtnType)) + sb.WriteString(fmt.Sprintf(" %s(opts?: RpcOpts): %s {\n", methodDecl.MethodName, rtnType)) } methodBody := fmt.Sprintf(" return WOS.wshServerRpcHelper_call(%q, %s, opts);\n", methodDecl.Command, dataName) sb.WriteString(methodBody) diff --git a/pkg/util/utilfn/utilfn.go b/pkg/util/utilfn/utilfn.go index 65cf46b0..a5a21632 100644 --- a/pkg/util/utilfn/utilfn.go +++ b/pkg/util/utilfn/utilfn.go @@ -20,6 +20,7 @@ import ( "os" "os/exec" "path/filepath" + "reflect" "regexp" "sort" "strconv" @@ -899,3 +900,15 @@ func RandomHexString(numHexDigits int) (string, error) { hexStr := hex.EncodeToString(bytes) return hexStr[:numHexDigits], nil // Return the exact number of hex digits } + +func GetJsonTag(field reflect.StructField) string { + jsonTag := field.Tag.Get("json") + if jsonTag == "" { + return "" + } + commaIdx := strings.Index(jsonTag, ",") + if commaIdx != -1 { + jsonTag = jsonTag[:commaIdx] + } + return jsonTag +} diff --git a/pkg/waveobj/waveobj.go b/pkg/waveobj/waveobj.go index 16a8b885..50fd86f8 100644 --- a/pkg/waveobj/waveobj.go +++ b/pkg/waveobj/waveobj.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/mitchellh/mapstructure" + "github.com/wavetermdev/thenextwave/pkg/util/utilfn" ) const ( @@ -127,7 +128,8 @@ func RegisterType(rtype reflect.Type) { if oidField.Type.Kind() != reflect.String { panic(fmt.Sprintf("OID field must be string for %v", rtype)) } - if oidField.Tag.Get("json") != OIDKeyName { + oidJsonTag := utilfn.GetJsonTag(oidField) + if oidJsonTag != OIDKeyName { panic(fmt.Sprintf("OID field json tag must be %q for %v", OIDKeyName, rtype)) } versionField, found := rtype.Elem().FieldByName(VersionGoFieldName) @@ -137,7 +139,8 @@ func RegisterType(rtype reflect.Type) { if versionField.Type.Kind() != reflect.Int { panic(fmt.Sprintf("Version field must be int for %v", rtype)) } - if versionField.Tag.Get("json") != VersionKeyName { + versionJsonTag := utilfn.GetJsonTag(versionField) + if versionJsonTag != VersionKeyName { panic(fmt.Sprintf("Version field json tag must be %q for %v", VersionKeyName, rtype)) } metaField, found := rtype.Elem().FieldByName(MetaGoFieldName) diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 93b11327..561d77b9 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -157,12 +157,12 @@ func ReadFullConfig() FullConfigType { if configFile == "-" { continue } - jsonTag := field.Tag.Get("json") + jsonTag := utilfn.GetJsonTag(field) if jsonTag == "-" || jsonTag == "" { continue } simpleMerge := field.Tag.Get("merge") == "" - fileName := field.Tag.Get("json") + ".json" + fileName := jsonTag + ".json" configPart, cerrs := ReadConfigPart(fileName, simpleMerge) fullConfig.ConfigErrors = append(fullConfig.ConfigErrors, cerrs...) if configPart != nil { @@ -177,7 +177,8 @@ func getConfigKeyType(configKey string) reflect.Type { ctype := reflect.TypeOf(SettingsType{}) for i := 0; i < ctype.NumField(); i++ { field := ctype.Field(i) - if field.Tag.Get("json") == configKey { + jsonTag := utilfn.GetJsonTag(field) + if jsonTag == configKey { return field.Type } } @@ -261,11 +262,7 @@ func jsonMarshalConfigInOrder(m waveobj.MetaMapType) ([]byte, error) { return buf.Bytes(), nil } -func SetBaseConfigValue(configKey string, val any) error { - ctype := getConfigKeyType(configKey) - if ctype == nil { - return fmt.Errorf("invalid config key: %s", configKey) - } +func SetBaseConfigValue(toMerge waveobj.MetaMapType) error { m, cerrs := ReadWaveHomeConfigFile(SettingsFile) if len(cerrs) > 0 { return fmt.Errorf("error reading config file: %v", cerrs[0]) @@ -273,13 +270,19 @@ func SetBaseConfigValue(configKey string, val any) error { if m == nil { m = make(waveobj.MetaMapType) } - if val == nil { - delete(m, configKey) - } else { - if reflect.TypeOf(val) != ctype { - return fmt.Errorf("invalid value type for %s: %T", configKey, val) + for configKey, val := range toMerge { + ctype := getConfigKeyType(configKey) + if ctype == nil { + return fmt.Errorf("invalid config key: %s", configKey) + } + if val == nil { + delete(m, configKey) + } else { + if reflect.TypeOf(val) != ctype { + return fmt.Errorf("invalid value type for %s: %T", configKey, val) + } + m[configKey] = val } - m[configKey] = val } return WriteWaveHomeConfigFile(SettingsFile, m) } diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go index f2e9c8be..3aef251d 100644 --- a/pkg/wshrpc/wshclient/wshclient.go +++ b/pkg/wshrpc/wshclient/wshclient.go @@ -147,6 +147,12 @@ func ResolveIdsCommand(w *wshutil.WshRpc, data wshrpc.CommandResolveIdsData, opt return resp, err } +// command "setconfig", wshserver.SetConfigCommand +func SetConfigCommand(w *wshutil.WshRpc, data waveobj.MetaMapType, opts *wshrpc.RpcOpts) error { + _, err := sendRpcRequestCallHelper[any](w, "setconfig", data, opts) + return err +} + // command "setmeta", wshserver.SetMetaCommand func SetMetaCommand(w *wshutil.WshRpc, data wshrpc.CommandSetMetaData, opts *wshrpc.RpcOpts) error { _, err := sendRpcRequestCallHelper[any](w, "setmeta", data, opts) diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 34946712..f1326325 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -90,6 +90,7 @@ type WshRpcInterface interface { StreamWaveAiCommand(ctx context.Context, request OpenAiStreamRequest) chan RespOrErrorUnion[OpenAIPacketType] StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData] TestCommand(ctx context.Context, data string) error + SetConfigCommand(ctx context.Context, data waveobj.MetaMapType) error // eventrecv is special, it's handled internally by WshRpc with EventListener EventRecvCommand(ctx context.Context, data WaveEvent) error diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go index bf55b8db..41d409e4 100644 --- a/pkg/wshrpc/wshserver/wshserver.go +++ b/pkg/wshrpc/wshserver/wshserver.go @@ -21,6 +21,7 @@ import ( "github.com/wavetermdev/thenextwave/pkg/filestore" "github.com/wavetermdev/thenextwave/pkg/waveai" "github.com/wavetermdev/thenextwave/pkg/waveobj" + "github.com/wavetermdev/thenextwave/pkg/wconfig" "github.com/wavetermdev/thenextwave/pkg/wcore" "github.com/wavetermdev/thenextwave/pkg/wlayout" "github.com/wavetermdev/thenextwave/pkg/wps" @@ -477,3 +478,7 @@ func (ws *WshServer) EventUnsubAllCommand(ctx context.Context) error { wps.Broker.UnsubscribeAll(rpcSource) return nil } + +func (ws *WshServer) SetConfigCommand(ctx context.Context, data waveobj.MetaMapType) error { + return wconfig.SetBaseConfigValue(data) +}