diff --git a/README.md b/README.md index e86fcd5..d1d069a 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ What is working: - Calling RPCs (unary + streaming) - Input and output of all protocol buffer types - Security: SSL is supported +- Saveing parameter in a config file (see below: Working with config Files) Some notable things which are not yet working: @@ -162,3 +163,50 @@ Be sure to search issues first to avoid duplicate entries. Please have a look at [CONTRIBUTE.md](CONTRIBUTING.md) for general information about contributing. Some more technical documentation can be found here: [Technical Documentation for Developers](doc/Developer.md). + +## Working with _config_ files +gWhisper supports config fies to save values for gWhisper parameter. +When provideing a setting for a parameter in the config file as well as in the cli command, gWhisper will use the parameter provided via cli. + +Example: +Setting the timeout for rpcs via the config `"RpcTimeoutInMs":"500"` and via the cli `--connectTimeoutMs=100`, gWhisper will use the latter. + + +### Structure +Use the following structure, if you want to create a config file: +```sh +{ + "configParameter": + { + "Ssl":null, + "SslSettings": + { + "ServerCertFile":"PathToFile", + "ClientCertFile":""PathToFile"", + "ClientKeyFile":""PathToFile"" + }, + "DisableCache":null, + "RpcTimeoutInMs":"TimeInMs", + "ConnectTimeoutBlubb":"TimeInMs" + } +} +``` + +### Set parameter +| Parameter | Settings | +| ------ | ------ | +| Ssl | "Yes" (string) : Enable a secure connection to the server
null: Disable secure connection | +| SslSettings | "AbsolutePath" (string): find path with pwd
null: | +| DisableCache | "Yes" (string) : Enable a secure connection to the server
null: Disable secure connection | +| RpcTimeoutInMs | "TimeInMs" (string):
null: | +| ConnectTimeout | "TimeInMs" (string):
null:| + +### Where to save the config file +gWhisper uses config file at /home/usr/.cache/gWhisperConfig.json` as default. +To the default, name your file `gWhisperConfig` and place it at `gWhisperConfig` + + + +Custom location +gWhisper supports config files at locations other than the default location. +To use custom path to the config file set the paramter `--configFile=` when executing gwhisper. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a748fe3..f5b7b0c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(gwhisper) add_subdirectory(version) add_subdirectory(utils) add_subdirectory(libLocalDescriptorCache) +add_subdirectory(libGwhisperSettings) get_filename_component(BIN_DIR_ABS ${CMAKE_BINARY_DIR}/. DIRECTORY) get_filename_component(SRC_DIR_ABS ${CMAKE_SOURCE_DIR}/. DIRECTORY) diff --git a/src/gwhisper/CMakeLists.txt b/src/gwhisper/CMakeLists.txt index c0812a4..e151f45 100644 --- a/src/gwhisper/CMakeLists.txt +++ b/src/gwhisper/CMakeLists.txt @@ -38,6 +38,7 @@ target_link_libraries ( ${TARGET_NAME} reflection version generateHelpString + libGwhisperSettings ) set_target_properties( diff --git a/src/gwhisper/gwhisper.cpp b/src/gwhisper/gwhisper.cpp index a8df901..78d71c2 100644 --- a/src/gwhisper/gwhisper.cpp +++ b/src/gwhisper/gwhisper.cpp @@ -20,6 +20,7 @@ #include #include #include // generated during build +#include "GwhisperSettings.hpp" using namespace ArgParse; @@ -56,6 +57,7 @@ int main(int argc, char **argv) std::string args = getArgsAsString(argc, argv); ParsedElement parseTree; ParseRc rc = grammarRoot->parse(args.c_str(), parseTree); + gWhisperSettings paramProxy(parseTree); if (parseTree.findFirstChild("DotExport") != "") { @@ -63,7 +65,7 @@ int main(int argc, char **argv) return 0; } - if (parseTree.findFirstChild("Complete") != "") + if (paramProxy.lookUpSetting("Complete", parseTree) != "") { bool completeDebug = (parseTree.findFirstChild("CompleteDebug") != ""); if (parseTree.findFirstChild("fish") != "") @@ -113,7 +115,7 @@ int main(int argc, char **argv) { //printf("\nchoice:\n%s", candidate->getDebugString().c_str()); printf("\n '%s'", candidate->getMatchedString().c_str()); - } + } std::cout << std::endl; } diff --git a/src/libCli/CMakeLists.txt b/src/libCli/CMakeLists.txt index 3b531d8..ef6bb47 100644 --- a/src/libCli/CMakeLists.txt +++ b/src/libCli/CMakeLists.txt @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +include(FetchContent) +FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.10.5/json.tar.xz) +FetchContent_MakeAvailable(json) set(TARGET_NAME "cli") set(TARGET_SRC @@ -34,12 +37,14 @@ target_link_libraries ( ${TARGET_NAME} PRIVATE reflection protoDoc + nlohmann_json::nlohmann_json ) target_link_libraries ( ${TARGET_NAME} PUBLIC gwhisperUtils DescDbProxy ArgParse + libGwhisperSettings ) target_include_directories(${TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/libCli/Call.cpp b/src/libCli/Call.cpp index e5dbef8..fe3a572 100644 --- a/src/libCli/Call.cpp +++ b/src/libCli/Call.cpp @@ -23,6 +23,7 @@ #include #include #include +#include "GwhisperSettings.hpp" // for detecting if we are writing stdout to terminal or to pipe/file #include @@ -32,6 +33,7 @@ #include using namespace ArgParse; +using json = nlohmann::json; namespace cli { @@ -42,9 +44,9 @@ namespace cli /// created. /// @param parseTree CLI argument parse tree, which will be used to detemrine /// which OputputFormatter to create. - std::unique_ptr createMessageFormatter(ParsedElement &parseTree) + std::unique_ptr createMessageFormatter(ParsedElement &parseTree, gWhisperSettings f_settingProxy) { - if(parseTree.findFirstChild("JsonOutput") != "") + if(f_settingProxy.lookUpSetting("JsonOutput", parseTree) != "") { return std::make_unique(); } @@ -59,13 +61,14 @@ namespace cli auto humanFormatter = std::make_unique(); // disable colored output if explicitly specified: - if (parseTree.findFirstChild("NoColor") != "") + //if (parseTree.findFirstChild("NoColor") != "") + if (f_settingProxy.lookUpSetting("NoColor", parseTree) != "") { humanFormatter->clearColorMap(); } // disable map output as key => value if explicitly specified: - if (parseTree.findFirstChild("NoSimpleMapOutput") != "") + if (f_settingProxy.lookUpSetting("NoSimpleMapOutput", parseTree) != "") { humanFormatter->disableSimpleMapOutput(); } @@ -73,7 +76,8 @@ namespace cli // automatically disable colored output, when outputting to something // else than a terminal (pipes, files, etc.), except we explicitly // request color mode: - if ((not isatty(fileno(stdout))) and (parseTree.findFirstChild("Color") == "")) + //if ((not isatty(fileno(stdout))) and (parseTree.findFirstChild("Color") == "")) + if ((not isatty(fileno(stdout))) and (f_settingProxy.lookUpSetting("Color", parseTree) == "")) { humanFormatter->clearColorMap(); } @@ -113,6 +117,7 @@ namespace cli int call(ParsedElement &parseTree) { + gWhisperSettings settingProxy(parseTree); std::string serviceName = parseTree.findFirstChild("Service"); std::string methodName = parseTree.findFirstChild("Method"); bool argsExist; @@ -121,7 +126,8 @@ namespace cli std::shared_ptr channel = ConnectionManager::getInstance().getChannel(serverAddress, parseTree); - if (not waitForChannelConnected(channel, getConnectTimeoutMs(&parseTree))) + std::string connectTimeoutStr = settingProxy.lookUpSetting("ConnectTimeout", parseTree); + if (not waitForChannelConnected(channel, getConnectTimeoutMs(connectTimeoutStr))) { std::cerr << "Error: channel connection attempt timed out" << std::endl; return -1; @@ -152,8 +158,7 @@ namespace cli std::optional> deadline; std::chrono::time_point defaultDeadline = std::chrono::system_clock::now() + std::chrono::milliseconds(30000); - - bool setTimeout = (parseTree.findFirstChild("rpcTimeout") != ""); + bool setTimeout = (settingProxy.lookUpSetting("RpcTimeoutInMs", parseTree) != ""); if(!setTimeout) { @@ -170,16 +175,20 @@ namespace cli if(setTimeout) { - if (parseTree.findFirstChild("manualInfiniteTimeout") != ""){ + std::string timeoutTime = settingProxy.lookUpSetting("RpcTimeoutInMs", parseTree); + if (settingProxy.lookUpSetting("RpcTimeoutInMs", parseTree) == "None") + { + // set infinite deadline for unary RPCs deadline = std::nullopt; } else { - std::string customTimeout = parseTree.findFirstChild("rpcTimeoutInMs"); + std::string customTimeout = settingProxy.lookUpSetting("RpcTimeoutInMs", parseTree); //check if none or number string + unsigned long customTimeoutMs; try { - customTimeoutMs = std::stoul(customTimeout, nullptr, 0); + customTimeoutMs = std::stoul(customTimeout, nullptr, 0); } catch(std::exception& e) { @@ -192,7 +201,7 @@ namespace cli grpc::testing::CliCall call(channel, methodStr, clientMetadata, deadline); - auto messageFormatter = createMessageFormatter(parseTree); + auto messageFormatter = createMessageFormatter(parseTree, settingProxy); auto messageParser = createMessageParser(parseTree); // NOTE: need to create and hold message factory here, as it holds @@ -217,7 +226,7 @@ namespace cli // Write all request messages (multiple in case of request stream) for (auto & message : requestMessages) { - if (parseTree.findFirstChild("PrintParsedMessage") != "") + if (settingProxy.lookUpSetting("PrintParsedMessage", parseTree) != "") { std::cout << "Request message:" << std::endl << messageFormatter->messageToString(*message, method->input_type()) << std::endl; diff --git a/src/libCli/ConnectionManager.cpp b/src/libCli/ConnectionManager.cpp index 74b8732..62158cf 100644 --- a/src/libCli/ConnectionManager.cpp +++ b/src/libCli/ConnectionManager.cpp @@ -22,6 +22,7 @@ #include #include #include +#include "GwhisperSettings.hpp" namespace cli { @@ -122,17 +123,13 @@ namespace cli ConnList connection; std::shared_ptr creds; std::shared_ptr channelCreds; + gWhisperSettings settingProxy(f_parseTree); // Todo: Replacef_parseTree with config object in signature of Connectionmanager - if (f_parseTree.findFirstChild("ssl") != "") + if (settingProxy.lookUpSetting("Ssl", f_parseTree) != "") { - // if --ssl set is set, check if user provides keys/ certs - bool clientCertOption = (f_parseTree.findFirstChild("OptionClientCert") != ""); - bool clientKeyOption = (f_parseTree.findFirstChild("OptionClientKey") != ""); - bool serverCertOption = (f_parseTree.findFirstChild("OptionServerCert") != ""); - - std::string sslClientCertPath = f_parseTree.findFirstChild("FileClientCert"); - std::string sslClientKeyPath = f_parseTree.findFirstChild("FileClientKey"); - std::string sslServerCertPath = f_parseTree.findFirstChild("FileServerCert"); + std::string sslClientCertPath = settingProxy.lookUpSetting("ClientCertFile", f_parseTree); + std::string sslClientKeyPath = settingProxy.lookUpSetting("ClientKeyFile", f_parseTree); + std::string sslServerCertPath = settingProxy.lookUpSetting("ServerCertFile", f_parseTree); // debugString = "CREATE SECURE CAHNNEL WITH USER-PROVIDED CREDENTIALS"; channelCreds = generateSSLCredentials(sslClientCertPath, sslClientKeyPath, sslServerCertPath); @@ -145,7 +142,6 @@ namespace cli } bool disableCache = (f_parseTree.findFirstChild("DisableCache") != ""); - // Timeout as chrono duration connection.descDbProxy = std::make_shared(disableCache, f_serverAddress, connection.channel, f_parseTree); connection.descPool = std::make_shared(connection.descDbProxy.get()); diff --git a/src/libCli/GrammarConstruction.cpp b/src/libCli/GrammarConstruction.cpp index afbf9b5..3cf55f1 100644 --- a/src/libCli/GrammarConstruction.cpp +++ b/src/libCli/GrammarConstruction.cpp @@ -455,21 +455,21 @@ namespace cli GrammarElement *optionsalt = f_grammarPool.createElement(); optionsalt->addChild(f_grammarPool.createElement("-h", "Help")); optionsalt->addChild(f_grammarPool.createElement("--help", "Help")); - optionsalt->addChild(f_grammarPool.createElement("--ssl", "ssl")); + optionsalt->addChild(f_grammarPool.createElement("--ssl", "Ssl")); GrammarElement *clientCert = f_grammarPool.createElement(); clientCert->addChild(f_grammarPool.createElement("--clientCert=", "OptionClientCert")); - clientCert->addChild(f_grammarPool.createElement(" %", '%', "FileClientCert")); + clientCert->addChild(f_grammarPool.createElement(" %", '%', "ClientCertFile")); optionsalt->addChild(clientCert); GrammarElement *clientKey = f_grammarPool.createElement(); clientKey->addChild(f_grammarPool.createElement("--clientKey=", "OptionClientKey")); - clientKey->addChild(f_grammarPool.createElement(" %", '%', "FileClientKey")); + clientKey->addChild(f_grammarPool.createElement(" %", '%', "ClientKeyFile")); optionsalt->addChild(clientKey); GrammarElement *serverCert = f_grammarPool.createElement(); serverCert->addChild(f_grammarPool.createElement("--serverCert=", "OptionServerCert")); - serverCert->addChild(f_grammarPool.createElement(" %", '%', "FileServerCert")); + serverCert->addChild(f_grammarPool.createElement(" %", '%', "ServerCertFile")); optionsalt->addChild(serverCert); GrammarElement *completeOption = f_grammarPool.createElement(); @@ -485,6 +485,11 @@ namespace cli completeDialectChoice->addChild(f_grammarPool.createElement("fish", "fish")); optionsalt->addChild(completeOption); + GrammarElement *config = f_grammarPool.createElement(); + config->addChild(f_grammarPool.createElement("--configFile=", "ConfigFile")); + config->addChild(f_grammarPool.createElement(" %", '%', "ConfigFilePath")); + optionsalt->addChild(config); + //completeOption->addChild(f_grammarPool.createElement("--complete", "Complete")); optionsalt->addChild(f_grammarPool.createElement("--debugComplete", "CompleteDebug")); optionsalt->addChild(f_grammarPool.createElement("--disableCache", "DisableCache")); @@ -501,19 +506,19 @@ namespace cli optionsalt->addChild(f_grammarPool.createElement("--noSimpleMapOutput", "NoSimpleMapOutput")); GrammarElement *timeout = f_grammarPool.createElement(); - timeout->addChild(f_grammarPool.createElement("--rpcTimeoutMilliseconds", "rpcTimeout")); + timeout->addChild(f_grammarPool.createElement("--rpcTimeoutMilliseconds")); //RPCTimeout timeout->addChild(f_grammarPool.createElement("=")); - GrammarElement *timeoutTime = f_grammarPool.createElement(); + GrammarElement *timeoutTime = f_grammarPool.createElement("RpcTimeoutInMs"); timeout->addChild(timeoutTime); - timeoutTime->addChild(f_grammarPool.createElement("[0-9]+", "rpcTimeoutInMs")); + timeoutTime->addChild(f_grammarPool.createElement("[0-9]+")); // RpcTimeoutInMs GrammarElement *manualInfiniteTimeout = f_grammarPool.createElement(); timeoutTime->addChild(manualInfiniteTimeout); - manualInfiniteTimeout->addChild(f_grammarPool.createElement("None", "manualInfiniteTimeout")); + manualInfiniteTimeout->addChild(f_grammarPool.createElement("None")); // manualInfiniteTimeout optionsalt->addChild(timeout); GrammarElement *timeoutOption = f_grammarPool.createElement(); timeoutOption->addChild(f_grammarPool.createElement("--connectTimeoutMilliseconds=")); - timeoutOption->addChild(f_grammarPool.createElement("[0-9]+", "connectTimeout")); + timeoutOption->addChild(f_grammarPool.createElement("[0-9]+", "ConnectTimeout")); optionsalt->addChild(timeoutOption); optionsalt->addChild(customOutputFormat); // FIXME FIXME FIXME: we cannot distinguish between --complete and --completeDebug.. this is a problem for arguments too, as we cannot guarantee, that we do not have an argument starting with the name of an other argument. diff --git a/src/libCli/cliUtils.cpp b/src/libCli/cliUtils.cpp index b204c7b..e1ea4db 100644 --- a/src/libCli/cliUtils.cpp +++ b/src/libCli/cliUtils.cpp @@ -9,15 +9,14 @@ namespace cli return result; } - uint32_t getConnectTimeoutMs(ArgParse::ParsedElement * f_parseTree, uint32_t f_default) + uint32_t getConnectTimeoutMs(std::string &f_connectTimeoutStr, uint32_t f_default) { // TODO: it would be nice to encode default values for options in the grammar // TODO: also it would be nice to provide grammar/parsed elements which can output c++ types other than strings (integers, etc.) - std::string connectTimeoutStr = f_parseTree->findFirstChild("connectTimeout"); uint32_t connectTimeoutMs = f_default; - if(connectTimeoutStr != "") + if(f_connectTimeoutStr != "") { - connectTimeoutMs = std::stol(connectTimeoutStr); + connectTimeoutMs = std::stol(f_connectTimeoutStr); } return connectTimeoutMs; } diff --git a/src/libCli/libCli/cliUtils.hpp b/src/libCli/libCli/cliUtils.hpp index 0556366..fa0b5fb 100644 --- a/src/libCli/libCli/cliUtils.hpp +++ b/src/libCli/libCli/cliUtils.hpp @@ -23,11 +23,11 @@ namespace cli /// @returns true if channel is connected, false if timeout exceeded and channel is still not in connected state. bool waitForChannelConnected(std::shared_ptr f_channel, uint32_t f_timeoutMs); - /// Retrieves the "connectTimeout" option from the parse tree - /// @param f_parseTree Parse-tree which should be searched for the option + /// Retrieves the "ConnectTimeout" option from the parse tree + /// @param f_connectTimeoutStr String holding the value for the timeout found in parsetree / config /// @param f_default default value returned, if parse-tree did not contain the option. /// @returns the value as an integer - uint32_t getConnectTimeoutMs(ArgParse::ParsedElement * f_parseTree, uint32_t f_default = 500); + uint32_t getConnectTimeoutMs(std::string & f_connectTimeoutStr, uint32_t f_default = 500); /// Convert a gRPC status code into a string. /// @param f_statusCode The status code to convert. diff --git a/src/libGwhisperSettings/CMakeLists.txt b/src/libGwhisperSettings/CMakeLists.txt new file mode 100644 index 0000000..9b7f952 --- /dev/null +++ b/src/libGwhisperSettings/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2022 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(FetchContent) +FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.10.5/json.tar.xz) +FetchContent_MakeAvailable(json) + +set(TARGET_NAME "libGwhisperSettings") +set(TARGET_SRC + GwhisperSettings.cpp + ) +add_library(${TARGET_NAME} ${TARGET_SRC}) + +target_link_libraries ( ${TARGET_NAME} + PRIVATE + reflection + protoDoc + ) +target_link_libraries ( ${TARGET_NAME} + PUBLIC + gwhisperUtils + DescDbProxy + ArgParse + nlohmann_json::nlohmann_json +) +target_include_directories(${TARGET_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/src/libGwhisperSettings/GwhisperSettings.cpp b/src/libGwhisperSettings/GwhisperSettings.cpp new file mode 100644 index 0000000..7e2291b --- /dev/null +++ b/src/libGwhisperSettings/GwhisperSettings.cpp @@ -0,0 +1,109 @@ +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "GwhisperSettings.hpp" +#include "libArgParse/ArgParse.hpp" +#include "../utils/gwhisperUtils.hpp" + +using json = nlohmann::json; + +void gWhisperSettings::parseConfigFile(const std::string &f_inputFile){ + // Parse JSON File into member SON object + std::ifstream ifs(f_inputFile.c_str()); + std::string configString = gwhisper::util::readFromFile(f_inputFile); + + + /*if (!ifs.is_open()) + { + std::cerr << "Error while opening config file at: " << f_inputFile << std::endl; + exit(EXIT_FAILURE); + } + // TODO: check for empty value with nlohmann::isempty()*/ + try + { + m_config = json::parse(configString); + } + catch(json::parse_error) + { + std::cerr << "Error while parsing config file at " << f_inputFile << std::endl; + std::cerr << "Try checking for JSON Syntax errors or empty JSON file." << std::endl; + exit(EXIT_FAILURE); + } + +} + +std::string gWhisperSettings::findParameterSettingInConfig(const std::string &f_parameter,const json &f_startElement) +{ + for (const auto &item : f_startElement.items()) + { + if(item.key() == f_parameter) + { + if(item.value().is_null()) + { + return ""; + } + else + { + return item.value(); + } + + } + + // Only Call recursion, if current element contains further elements + if (item.value().is_object()) + { + return findParameterSettingInConfig(f_parameter, item.value()); + } + } + return ""; +} + +std::string gWhisperSettings::lookUpSetting(const std::string &f_parameter, ArgParse::ParsedElement &f_parseTree) +{ + bool containsParameter; + std::string setting; + + if (f_parseTree.findFirstChild(f_parameter) != "") + { + return f_parseTree.findFirstChild(f_parameter); + } + else + { + setting = findParameterSettingInConfig(f_parameter, m_config); + return setting; + } +} + +gWhisperSettings::gWhisperSettings(ArgParse::ParsedElement &f_parseTree){ //ParameterKey + if(m_config.is_null()) + { + bool useCustomPath = f_parseTree.findFirstChild("ConfigFile")!= ""; + const char* home = std::getenv("HOME"); + std::string defaultPath = "/.config/gWhisperConfig.json"; + std::string inputFile = home + defaultPath; + + if(useCustomPath) + { + inputFile = f_parseTree.findFirstChild("ConfigFilePath"); + } + + parseConfigFile(inputFile); + } +} + +gWhisperSettings::~gWhisperSettings(){} \ No newline at end of file diff --git a/src/libGwhisperSettings/GwhisperSettings.hpp b/src/libGwhisperSettings/GwhisperSettings.hpp new file mode 100644 index 0000000..284f8a0 --- /dev/null +++ b/src/libGwhisperSettings/GwhisperSettings.hpp @@ -0,0 +1,52 @@ +// Copyright 2022 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include "libArgParse/ArgParse.hpp" + +using json = nlohmann::json; + +class gWhisperSettings{ + public: + /// Retrieves inital config object. + // Uses the parse tree only to retrieve the location of the config gile. + /// @param f_parseTree + gWhisperSettings(ArgParse::ParsedElement &f_parseTree); + ~gWhisperSettings(); + + // /Searches setting for a parameter in the parse tree. + /// Searches for in the config file, if parameter is not found in the parse tree. + /// @param f_parameter Name of paramter to retrieve seting for. + /// @param f_parseTree Current grammar tree. + /// @return Returns value of the parameter as string. Returns an empty string + /// if the parameter is not set. + std::string lookUpSetting(const std::string &f_parameter, ArgParse::ParsedElement &f_parseTree); + + + private: + /// Parses a valid json file into a json object. + /// @param f_inputFile Path to input file. + void parseConfigFile(const std::string &f_inputFile); + + /// Recursively searches the value of a key in a (nested) json object + /// @param f_parameter Name of the parameter to search setting for. + /// @param f_startElement Node of config fike, from where the search starts. + /// @return returns setting for parameter as a string. If parameter is not set via config file or + /// the parameter is set tu null, an empty string is returned + std::string findParameterSettingInConfig(const std::string &f_parameter, const json &f_startElement); + + json m_config; + std::vector m_configParameters; +}; \ No newline at end of file diff --git a/src/libLocalDescriptorCache/CMakeLists.txt b/src/libLocalDescriptorCache/CMakeLists.txt index cc8dd8b..3d6ec02 100644 --- a/src/libLocalDescriptorCache/CMakeLists.txt +++ b/src/libLocalDescriptorCache/CMakeLists.txt @@ -28,6 +28,7 @@ target_link_libraries(${TARGET_NAME} ${TARGET_NAME}_protobuf PRIVATE + libGwhisperSettings gwhisperUtils version reflection diff --git a/src/libLocalDescriptorCache/DescDbProxy.cpp b/src/libLocalDescriptorCache/DescDbProxy.cpp index a8e25ed..d2692f6 100644 --- a/src/libLocalDescriptorCache/DescDbProxy.cpp +++ b/src/libLocalDescriptorCache/DescDbProxy.cpp @@ -17,6 +17,7 @@ #include "libCli/ConnectionManager.hpp" #include "libArgParse/ArgParse.hpp" #include "LocalDescDb.pb.h" +#include "GwhisperSettings.hpp" #include #include @@ -151,7 +152,9 @@ void DescDbProxy::repopulateLocalDb(localDescDb::Host& f_out_host, const std::st void DescDbProxy::useReflection(const std::string &f_hostAddress) { - if (not cli::waitForChannelConnected(m_channel, cli::getConnectTimeoutMs(&m_parseTree))) + gWhisperSettings settingProxy(m_parseTree); + std::string connectTimeoutStr = settingProxy.lookUpSetting("ConnectTimeout", m_parseTree); + if (not cli::waitForChannelConnected(m_channel, cli::getConnectTimeoutMs(connectTimeoutStr))) { std::cerr << "Error: Could not establish Channel. Try checking network connection, hostname or SSL credentials." << std::endl; exit(EXIT_FAILURE); diff --git a/src/utils/gwhisperUtils.cpp b/src/utils/gwhisperUtils.cpp index 3b12262..4714a13 100644 --- a/src/utils/gwhisperUtils.cpp +++ b/src/utils/gwhisperUtils.cpp @@ -31,7 +31,7 @@ namespace gwhisper const char* home = std::getenv("HOME"); if (!home) { - std::cerr << "Error while fetching home envoronment. Try checking your home environment variable." << std::endl; + std::cerr << "Error while fetching home environment. Try checking your home environment variable." << std::endl; exit(EXIT_FAILURE); } std::string subPath = f_path.substr(1); // take everything after '~' diff --git a/testfile.json b/testfile.json new file mode 100644 index 0000000..f724a8f --- /dev/null +++ b/testfile.json @@ -0,0 +1,13 @@ +{ + "configParameter": { + "Ssl": null, + "SslSettings": { + "ServerCertFile": null, + "ClientCertFile": null, + "ClientKeyFile": null + }, + "DisableCache": null, + "RpcTimeoutInMs": null, + "connectTimeout": null + } +} \ No newline at end of file diff --git a/tests/emptyConfig.json b/tests/emptyConfig.json new file mode 100644 index 0000000..e69de29 diff --git a/tests/functionTests/CMakeLists.txt b/tests/functionTests/CMakeLists.txt index 92dff17..8652ac7 100644 --- a/tests/functionTests/CMakeLists.txt +++ b/tests/functionTests/CMakeLists.txt @@ -33,4 +33,7 @@ add_test(NAME RpcTimeoutTests add_test(NAME CacheTests COMMAND python ${PROJECT_SOURCE_DIR}/tests/functionTests/runFunctionTest.py ${gwhisper_exec} ${testserver_exec} ${CMAKE_CURRENT_SOURCE_DIR}/resources/ ${PROJECT_BINARY_DIR}/tests/testServer/cert-key-pair/ ${PROJECT_SOURCE_DIR}/tests/functionTests/cacheFunctionTests.txt) +add_test(NAME ConfigTests + COMMAND python ${PROJECT_SOURCE_DIR}/tests/functionTests/runFunctionTest.py ${gwhisper_exec} ${testserver_exec} ${CMAKE_CURRENT_SOURCE_DIR}/resources/ ${PROJECT_BINARY_DIR}/tests/testServer/cert-key-pair/ ${PROJECT_SOURCE_DIR}/tests/functionTests/configTests.txt) + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/resources/data.bin DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/resources) diff --git a/tests/functionTests/cacheFunctionTests.txt b/tests/functionTests/cacheFunctionTests.txt index 02047bc..e3d21ab 100644 --- a/tests/functionTests/cacheFunctionTests.txt +++ b/tests/functionTests/cacheFunctionTests.txt @@ -90,14 +90,14 @@ RPC succeeded :D rm ~/.cache/gwhisper/DescriptorCache.bin #END_CMD #EXEC_CMD -@@CMD@@ --ssl --clientCert=@@PTC@@/client_crt.pem --serverCert=@@PTC@@/server_crt.pem localhost:50443 examples.NestedTypeRpcs echoNestedMaps +@@CMD@@ --configFile=@@testResources@@/testDefaultConfig.json --ssl --clientCert=@@PTC@@/client_crt.pem --serverCert=@@PTC@@/server_crt.pem localhost:50443 examples.NestedTypeRpcs echoNestedMaps /Error: Could not establish Channel. Try checking network connection, hostname or SSL credentials.* #END_CMD #END_TEST #START_TEST Invalid secure RPC with existing cache #EXEC_CMD -@@CMD@@ --ssl --clientCert=@@PTC@@/client_crt.pem --clientKey=@@PTC@@/client_key.pem --serverCert=@@PTC@@/server_crt.pem localhost:50443 examples.ScalarTypeRpcs negateBool m_bool=0 +@@CMD@@ --configFile=@@testResources@@/testDefaultConfig.json --ssl --clientCert=@@PTC@@/client_crt.pem --clientKey=@@PTC@@/client_key.pem --serverCert=@@PTC@@/server_crt.pem localhost:50443 examples.ScalarTypeRpcs negateBool m_bool=0 /.* Received message: | m_bool = true RPC succeeded :D @@ -107,7 +107,7 @@ ls ~/.cache/gwhisper/ DescriptorCache.bin #END_CMD #EXEC_CMD -@@CMD@@ --ssl --clientCert=@@PTC@@/client_crt.pem --serverCert=@@PTC@@/server_crt.pem localhost:50443 examples.NestedTypeRpcs echoNestedMaps +@@CMD@@ --configFile=@@testResources@@/testDefaultConfig.json --ssl --clientCert=@@PTC@@/client_crt.pem --serverCert=@@PTC@@/server_crt.pem localhost:50443 examples.NestedTypeRpcs echoNestedMaps Error: channel connection attempt timed out #END_CMD #END_TEST diff --git a/tests/functionTests/completionTests.txt b/tests/functionTests/completionTests.txt index 14b6d78..8a2509c 100644 --- a/tests/functionTests/completionTests.txt +++ b/tests/functionTests/completionTests.txt @@ -23,6 +23,7 @@ Possible Candidates: '--clientKey=' '--serverCert=' '--complete' + '--configFile=' '--debugComplete ' '--disableCache ' '--dot ' diff --git a/tests/functionTests/completionTests_fish.txt b/tests/functionTests/completionTests_fish.txt index 138f656..f27fe45 100644 --- a/tests/functionTests/completionTests_fish.txt +++ b/tests/functionTests/completionTests_fish.txt @@ -24,6 +24,7 @@ Possible Candidates: '--clientKey=' '--serverCert=' '--complete' + '--configFile=' '--debugComplete ' '--disableCache ' '--dot ' diff --git a/tests/functionTests/configTests.txt b/tests/functionTests/configTests.txt new file mode 100644 index 0000000..ab8f26a --- /dev/null +++ b/tests/functionTests/configTests.txt @@ -0,0 +1,55 @@ +# Copyright 2022 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +############################################################################## +# Valid config file (.cache/gWhisperConfigFile.json) +############################################################################## + +#START_TEST RPC using config settings with config File location +@@CMD@@ --configFile=@@testResources@@/validConfig.json --disableCache --ssl localhost:50443 examples.NestedTypeRpcs echoNestedMaps +Warning: no Fields found in parseTree for message 'NestedMaps' +/.*Received message: +| number_and_string..... = [NOT SET] +| sub_message........... = [NOT SET] +| simple_map_int[0/0] = {} +| simple_map_string[0/0] = {} +RPC succeeded :D +#END_TEST + +#START_TEST RPC with cli settings that overide config settings (set Timeout to 100) in config +@@CMD@@ --disableCache --configFile=@@testResources@@/validConfig.json --rpcTimeoutMilliseconds=2000 localhost:50051 examples.StatusHandling rpcSleepForSeconds number=1 +/.* Received message: + +RPC succeeded :D +#END_TEST + +############################################################################## +# Invalid config file +############################################################################## +#START_TEST RPC cannot open config file +@@CMD@@ --configFile=@@testResources@@/validConfig.jso --complete localhost:50443 examples.StatusHandling neverEndingRpc +/File not found at:.* +#END_TEST + +#START_TEST Trying to open empty json file +@@CMD@@ --configFile=@@testResources@@/emptyConfig.json --complete localhost:50051 +/Error while parsing config file at.* +Try checking for JSON Syntax errors or empty JSON file. +#END_TEST + +#START_TEST RPC with invalid json format in config File +@@CMD@@ --configFile=@@testResources@@/invalidJsonConfig.json --complete localhost:50051 +/Error while parsing config file at.* +Try checking for JSON Syntax errors or empty JSON file. +#END_TEST \ No newline at end of file diff --git a/tests/functionTests/resources/emptyConfig.json b/tests/functionTests/resources/emptyConfig.json new file mode 100644 index 0000000..e69de29 diff --git a/tests/functionTests/resources/invalidJsonConfig.json b/tests/functionTests/resources/invalidJsonConfig.json new file mode 100644 index 0000000..8dbd36c --- /dev/null +++ b/tests/functionTests/resources/invalidJsonConfig.json @@ -0,0 +1,17 @@ +{ + "configParameter": + { + "Ssl":null + "SslSettings": + { + "ServerCertFile":null, + "ClientCertFile":null, + "ClientKeyFile":null + }, + + "DisableCache":null, + + "RpcTimeoutInMs":"500", + "ConnectTimeout":"200" + } +} diff --git a/tests/functionTests/resources/testDefaultConfig.json b/tests/functionTests/resources/testDefaultConfig.json new file mode 100644 index 0000000..03c2704 --- /dev/null +++ b/tests/functionTests/resources/testDefaultConfig.json @@ -0,0 +1,17 @@ +{ + "configParameter": + { + "Ssl":null, + "SslSettings": + { + "ServerCertFile":null, + "ClientCertFile":null, + "ClientKeyFile":null + }, + + "DisableCache":null, + + "RpcTimeoutInMs":null, + "ConnectTimeout":null + } +} \ No newline at end of file diff --git a/tests/functionTests/resources/validConfig.json b/tests/functionTests/resources/validConfig.json new file mode 100644 index 0000000..82a72a3 --- /dev/null +++ b/tests/functionTests/resources/validConfig.json @@ -0,0 +1,17 @@ +{ + "configParameter": + { + "Ssl":null, + "SslSettings": + { + "ServerCertFile":"/home/anna/gWhisper/build/tests/testServer/cert-key-pair/server_crt.pem", + "ClientCertFile":"/home/anna/gWhisper/build/tests/testServer/cert-key-pair/client_crt.pem", + "ClientKeyFile":"/home/anna/gWhisper/build/tests/testServer/cert-key-pair/client_key.pem" + }, + + "DisableCache":null, + + "RpcTimeoutInMs":"100", + "ConnectTimeout":"100" + } +} \ No newline at end of file diff --git a/tests/functionTests/rpcTimeoutFunctionTests.txt b/tests/functionTests/rpcTimeoutFunctionTests.txt index 5e9566a..6bd38bd 100644 --- a/tests/functionTests/rpcTimeoutFunctionTests.txt +++ b/tests/functionTests/rpcTimeoutFunctionTests.txt @@ -68,7 +68,7 @@ RPC succeeded :D #END_TEST #START_TEST check value default timeout for unary RPCs -@@CMD@@ --disableCache localhost:50051 examples.StatusHandling rpcSleepForSeconds number=9 +@@CMD@@ --configFile=/@@testResources@@/testDefaultConfig.json --disableCache localhost:50051 examples.StatusHandling rpcSleepForSeconds number=9 /.* Received message: RPC succeeded :D diff --git a/tests/functionTests/sslFunctionTests.txt b/tests/functionTests/sslFunctionTests.txt index f4cb899..22df62d 100644 --- a/tests/functionTests/sslFunctionTests.txt +++ b/tests/functionTests/sslFunctionTests.txt @@ -39,12 +39,12 @@ RPC succeeded :D #END_TEST #START_TEST secure gRPC with missing Certificate/Key and server requires cert-key-pair -@@CMD@@ --disableCache --ssl --clientCert=@@PTC@@/client_crt.pem --serverCert=@@PTC@@/server_crt.pem localhost:50443 examples.NestedTypeRpcs echoNestedMaps +@@CMD@@ --configFile=@@testResources@@/testDefaultConfig.json --disableCache --ssl --clientCert=@@PTC@@/client_crt.pem --serverCert=@@PTC@@/server_crt.pem localhost:50443 examples.NestedTypeRpcs echoNestedMaps /Error: Could not establish Channel. Try checking network connection, hostname or SSL credentials.* #END_TEST #START_TEST secure gRPC with missing serverCert at default location -@@CMD@@ --disableCache --ssl localhost:50052 examples.NestedTypeRpcs echoNestedMaps +@@CMD@@ --configFile=@@testResources@@/testDefaultConfig.json --disableCache --ssl localhost:50052 examples.NestedTypeRpcs echoNestedMaps /.*SSL_ERROR_SSL.* ?.*SSL_ERROR_SSL.* /Error: Could not establish Channel. Try checking network connection, hostname or SSL credentials* diff --git a/tests/invalidJsonConfig.json b/tests/invalidJsonConfig.json new file mode 100644 index 0000000..8dbd36c --- /dev/null +++ b/tests/invalidJsonConfig.json @@ -0,0 +1,17 @@ +{ + "configParameter": + { + "Ssl":null + "SslSettings": + { + "ServerCertFile":null, + "ClientCertFile":null, + "ClientKeyFile":null + }, + + "DisableCache":null, + + "RpcTimeoutInMs":"500", + "ConnectTimeout":"200" + } +}