From 22b2501a8faaefd0dada8dd999702f848e6ee479 Mon Sep 17 00:00:00 2001 From: Ken Rowland Date: Mon, 10 Dec 2018 15:24:07 -0500 Subject: [PATCH] HPCC-20458 Allow File Access keys to be defined in the environment Add new CLI envmod for processing modification templates to modify environment files. Update modification template support. Add unit tests for new features. Signed-off-by: Ken Rowland --- configuration/CMakeLists.txt | 3 +- configuration/cli/CMakeLists.txt | 17 + configuration/cli/envmod/CMakeLists.txt | 34 ++ configuration/cli/envmod/envmod.cpp | 414 ++++++++++++++++++ .../configmgr/configmgrlib/CMakeLists.txt | 63 +-- .../configmgr/configmgrlib/EnvironmentMgr.cpp | 27 +- .../configmgr/configmgrlib/EnvironmentMgr.hpp | 9 +- .../configmgrlib/EnvironmentNode.cpp | 32 +- .../configmgrlib/EnvironmentNode.hpp | 4 +- .../configmgrlib/EnvironmentValue.cpp | 2 +- .../configmgrlib/XMLEnvironmentLoader.cpp | 2 +- .../configmgrlib/XMLEnvironmentMgr.cpp | 11 +- .../configmgrlib/XMLEnvironmentMgr.hpp | 1 + .../mod_template_support/EnvModTemplate.cpp | 314 ++++++++----- .../mod_template_support/EnvModTemplate.hpp | 23 +- ...HostNameInput.cpp => HostNameVariable.cpp} | 4 +- ...HostNameInput.hpp => HostNameVariable.hpp} | 11 +- ...geInput.cpp => IPAddressRangeVariable.cpp} | 6 +- ...geInput.hpp => IPAddressRangeVariable.hpp} | 11 +- ...AddressInput.cpp => IPAddressVariable.cpp} | 4 +- ...AddressInput.hpp => IPAddressVariable.hpp} | 11 +- .../mod_template_support/Operation.cpp | 94 +++- .../mod_template_support/Operation.hpp | 33 +- .../OperationCreateNode.cpp | 71 +-- .../OperationCreateNode.hpp | 16 +- .../OperationDeleteNode.cpp | 27 ++ ...rationNoop.hpp => OperationDeleteNode.hpp} | 15 +- .../OperationFindNode.cpp | 93 ++++ .../OperationFindNode.hpp | 45 ++ .../OperationModifyNode.cpp | 65 +++ .../OperationModifyNode.hpp | 44 ++ .../mod_template_support/OperationNoop.cpp | 25 -- .../TemplateException.cpp | 1 - .../TemplateException.hpp | 13 +- .../TemplateExecutionException.hpp | 51 +++ .../{Input.cpp => Variable.cpp} | 39 +- .../{Input.hpp => Variable.hpp} | 30 +- .../{Inputs.cpp => Variables.cpp} | 66 +-- .../{Inputs.hpp => Variables.hpp} | 16 +- .../configschema/templates/AddKeys.json | 72 +++ .../templates/schema/ModTemplateSchema.json | 399 ++++++++++++----- .../configmgr/ConfigMgrTemplateTests.cpp | 212 ++++++--- .../schema/environments/CMakeLists.txt | 14 +- .../schema/environments/createnode_test1.xml | 6 + .../schema/environments/findnodes_test.xml | 10 + .../schema/environments/template_test.xml | 2 + .../unittests/configmgr/schema/xsd/Simple.xsd | 20 +- .../configmgr/templates/CreateNodeTest1.json | 60 +++ .../configmgr/templates/FindNodeTest1.json | 58 +++ .../configmgr/templates/OperationsTest1.json | 32 +- .../configmgr/templates/OperationsTest2.json | 28 ++ .../configmgr/templates/OperationsTest3.json | 28 ++ .../configmgr/templates/OperationsTest4.json | 38 ++ .../configmgr/templates/OperationsTest5.json | 38 ++ .../templates/SubstitutionTest-1.json | 57 +-- 55 files changed, 2203 insertions(+), 618 deletions(-) create mode 100644 configuration/cli/CMakeLists.txt create mode 100644 configuration/cli/envmod/CMakeLists.txt create mode 100644 configuration/cli/envmod/envmod.cpp rename configuration/configmgr/configmgrlib/mod_template_support/{HostNameInput.cpp => HostNameVariable.cpp} (91%) rename configuration/configmgr/configmgrlib/mod_template_support/{HostNameInput.hpp => HostNameVariable.hpp} (79%) rename configuration/configmgr/configmgrlib/mod_template_support/{IPAddressRangeInput.cpp => IPAddressRangeVariable.cpp} (97%) rename configuration/configmgr/configmgrlib/mod_template_support/{IPAddressRangeInput.hpp => IPAddressRangeVariable.hpp} (77%) rename configuration/configmgr/configmgrlib/mod_template_support/{IPAddressInput.cpp => IPAddressVariable.cpp} (94%) rename configuration/configmgr/configmgrlib/mod_template_support/{IPAddressInput.hpp => IPAddressVariable.hpp} (79%) create mode 100644 configuration/configmgr/configmgrlib/mod_template_support/OperationDeleteNode.cpp rename configuration/configmgr/configmgrlib/mod_template_support/{OperationNoop.hpp => OperationDeleteNode.hpp} (67%) create mode 100644 configuration/configmgr/configmgrlib/mod_template_support/OperationFindNode.cpp create mode 100644 configuration/configmgr/configmgrlib/mod_template_support/OperationFindNode.hpp create mode 100644 configuration/configmgr/configmgrlib/mod_template_support/OperationModifyNode.cpp create mode 100644 configuration/configmgr/configmgrlib/mod_template_support/OperationModifyNode.hpp delete mode 100644 configuration/configmgr/configmgrlib/mod_template_support/OperationNoop.cpp create mode 100644 configuration/configmgr/configmgrlib/mod_template_support/TemplateExecutionException.hpp rename configuration/configmgr/configmgrlib/mod_template_support/{Input.cpp => Variable.cpp} (74%) rename configuration/configmgr/configmgrlib/mod_template_support/{Input.hpp => Variable.hpp} (62%) rename configuration/configmgr/configmgrlib/mod_template_support/{Inputs.cpp => Variables.cpp} (73%) rename configuration/configmgr/configmgrlib/mod_template_support/{Inputs.hpp => Variables.hpp} (77%) create mode 100644 initfiles/componentfiles/configschema/templates/AddKeys.json create mode 100644 testing/unittests/configmgr/schema/environments/createnode_test1.xml create mode 100644 testing/unittests/configmgr/schema/environments/findnodes_test.xml create mode 100644 testing/unittests/configmgr/templates/CreateNodeTest1.json create mode 100644 testing/unittests/configmgr/templates/FindNodeTest1.json create mode 100644 testing/unittests/configmgr/templates/OperationsTest2.json create mode 100644 testing/unittests/configmgr/templates/OperationsTest3.json create mode 100644 testing/unittests/configmgr/templates/OperationsTest4.json create mode 100644 testing/unittests/configmgr/templates/OperationsTest5.json diff --git a/configuration/CMakeLists.txt b/configuration/CMakeLists.txt index 7f07597f482..63ff7ebf43c 100644 --- a/configuration/CMakeLists.txt +++ b/configuration/CMakeLists.txt @@ -1,5 +1,5 @@ ################################################################################ -# HPCC SYSTEMS software Copyright (C) 2016 HPCC Systems. +# HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,3 +15,4 @@ ################################################################################ HPCC_ADD_SUBDIRECTORY (configmgr) +HPCC_ADD_SUBDIRECTORY (cli) diff --git a/configuration/cli/CMakeLists.txt b/configuration/cli/CMakeLists.txt new file mode 100644 index 00000000000..dccf62a128c --- /dev/null +++ b/configuration/cli/CMakeLists.txt @@ -0,0 +1,17 @@ +################################################################################ +# HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems. +# +# 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. +################################################################################ + +HPCC_ADD_SUBDIRECTORY (envmod) diff --git a/configuration/cli/envmod/CMakeLists.txt b/configuration/cli/envmod/CMakeLists.txt new file mode 100644 index 00000000000..afb8a51ca51 --- /dev/null +++ b/configuration/cli/envmod/CMakeLists.txt @@ -0,0 +1,34 @@ +################################################################################ +# HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems. +# +# 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. +################################################################################ + +project ( envmod ) + +SET (SRC_FILES + envmod.cpp +) + +INCLUDE_DIRECTORIES( + ${CMAKE_BINARY_DIR} + ${HPCC_SOURCE_DIR}/system/include + ${HPCC_SOURCE_DIR}/configuration/configmgr/configmgrlib + ${HPCC_SOURCE_DIR}/configuration/configmgr/RapidJSON/include +) + +HPCC_ADD_EXECUTABLE ( envmod ${SRC_FILES} ) + +INSTALL ( TARGETS envmod RUNTIME DESTINATION ${EXEC_DIR} ) + +TARGET_LINK_LIBRARIES(envmod configmgr) diff --git a/configuration/cli/envmod/envmod.cpp b/configuration/cli/envmod/envmod.cpp new file mode 100644 index 00000000000..dc65b0c41a1 --- /dev/null +++ b/configuration/cli/envmod/envmod.cpp @@ -0,0 +1,414 @@ +/*############################################################################## + HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®. + 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 +#include "build-config.h" +#include "platform.h" +#include +#include +#include +#include "EnvironmentMgr.hpp" +#include "Exceptions.hpp" +#include "mod_template_support/EnvModTemplate.hpp" +#include "mod_template_support/TemplateExecutionException.hpp" + +// +// Inputs file format (json) +// +// { +// "input-name" : [ ], +// ... +// } + +bool processOptions(int argc, char *vargv[]); +bool validate(); +std::string getNextArg(int argc, char *argv[], int idx); +bool dirExists(const std::string &dirName); +bool fileExists(const std::string &fileName); +std::vector splitString(const std::string &input, const std::string &delim); + +// Default configuration directories +EnvironmentType envType = XML; +std::string masterSchemaFile = "environment.xsd"; +std::string configSchemaDir = COMPONENTFILES_DIR PATHSEPSTR "configschema" PATHSEPSTR "xsd" PATHSEPSTR; +std::string modTemplateFile; +std::string modTemplateSchemaFile = COMPONENTFILES_DIR PATHSEPSTR "configschema" PATHSEPSTR "templates" PATHSEPSTR "schema" PATHSEPSTR "ModTemplateSchema.json"; +std::string configSchemaPluginsDir = "xsd" PATHSEPSTR "plugins" PATHSEPSTR; +std::string envFile, envOutputFile; +struct InputDef { + std::string inputName; + std::string inputValue; +}; + +std::vector userInputs; +bool listInputs = false; + +EnvironmentMgr *pEnvMgr = nullptr; +EnvModTemplate *pTemplate = nullptr; + +class CliException : public std::exception +{ + public: + + explicit CliException(std::string reason) : m_reason(std::move(reason)) { } + virtual const char *what() const noexcept override + { + return m_reason.c_str(); + } + + private: + + std::string m_reason; +}; + + +void usage() +{ + // + // usage below documents options + std::cout << std::endl; + std::cout << "envmod envfile" << std::endl; + std::cout << std::endl; + std::cout << " Configuration Options:" << std::endl; + std::cout << " -d --schema-dir : path to schema files. default (" << configSchemaDir << ")" << std::endl; + std::cout << " -p --schema-plugins-dir : path to plugin files, default (" << configSchemaPluginsDir << ")" << std::endl; + std::cout << " -m --master-config : name of master schema file, default (" << masterSchemaFile << ")" << std::endl; + std::cout << std::endl; + std::cout << " Execution Options:" << std::endl; + std::cout << " -t --template : filepath to modification template - required" << std::endl; + std::cout << " -e --env : Optional filepath to environment file to modify" << std::endl; + std::cout << " Omit to validate the modification template" << std::endl; + std::cout << " --inputs : List the template inputs. If this option is specified, the template" << std::endl; + std::cout << " inputs are listed and the command exits." << std::endl; + std::cout << " --input-file : Optional full path to a file with defined inputs for the template" << std::endl; + std::cout << " These would be combined with any command line inputs" << std::endl; + std::cout << " -i --input : Assign the indicated value to the variable input." << std::endl; + std::cout << " The input must be defined in the template" << std::endl; + std::cout << " value may be a comma separated list (no blanks) for multi-value inputs" << std::endl; + std::cout << " Inputs given on the command line override any in an input file (--input-file)" << std::endl; + std::cout << " -o --output [fullpath] : Optional. If present, results of applying template are saved." << std::endl; + std::cout << " If fullpath is specified, results written to this file, " << std::endl; + std::cout << " If not specified, input env file is overwritten" << std::endl; + std::cout << " Omit the -o option to test the modification template" << std::endl; + std::cout << std::endl; +} + + +int main(int argc, char *argv[]) +{ + if (argc == 1) + { + usage(); + return 0; + } + + // + // Read options and validate + if (!processOptions(argc, argv)) + { + return 1; // get out now + } + + if (!validate()) + { + usage(); + return 1; + } + + // + // Create an environment manager reference and load the schema + try + { + pEnvMgr = getEnvironmentMgrInstance(envType); + std::cout << "Loading schema defined by " << masterSchemaFile << "..."; + + // note that these are hardcoded for HPCC at this time, but could be made into options + std::map cfgParms; + cfgParms["buildset"] = "buildset.xml"; // Not used right now, and probably never will be + cfgParms["support_libs"] = "libcfgsupport_addrequiredinstances"; + std::string pluginsPath = configSchemaPluginsDir; + + if (!pEnvMgr->loadSchema(configSchemaDir, masterSchemaFile, cfgParms)) + { + throw CliException(pEnvMgr->getLastSchemaMessage()); + } + } + catch (const ParseException &pe) + { + std::cout << "There was a problem creating the environment manager instance: " << pe.what() << std::endl; + } + catch(const CliException &e) + { + std::cout << "There was a problem loading the schema: " << e.what() << std::endl; + } + + + // + // Create the modification template + try + { + pTemplate = new EnvModTemplate(pEnvMgr, modTemplateSchemaFile); + pTemplate->loadTemplateFromFile(modTemplateFile); + } + catch (const TemplateException &te) + { + std::cout << "There was a problem loading the modification template: " << te.what() << std::endl; + return 1; + } + + // + // If list inputs was given, list them and get out + if (listInputs) + { + std::cout << "Template inputs:" << std::endl; + auto templateInputs = pTemplate->getVariables(); + for (auto &pInput : templateInputs) + { + std::cout << pInput->getName() << " - " << pInput->getDescription() << std::endl; + } + return 0; // get out + } + + // + // Process the input file if given here + + // + // If the user provided any inputs, assign them + if (!userInputs.empty()) + { + for (auto &userInput: userInputs) + { + try + { + auto pInput = pTemplate->getVariable(userInput.inputName); + auto values = splitString(userInput.inputValue, ","); + for (auto &value: values) + { + pInput->addValue(value); + } + } + catch (const TemplateException &te) + { + std::cout << "There was a problem: " << te.what() << std::endl; + return 0; // get out + } + } + } + + // + // If there is an environment, load it and apply + if (!envFile.empty()) + { + if (!pEnvMgr->loadEnvironment(envFile)) + { + std::cout << "There was a problem loading the environment: " << std::endl << pEnvMgr->getLastEnvironmentMessage() << std::endl; + return 1; + } + + try + { + pTemplate->execute(); + } + catch (const TemplateExecutionException &te) + { + std::cout << te.what() << std::endl; + return 1; + } + + // + // Write results + if (!envOutputFile.empty()) + { + pEnvMgr->saveEnvironment(envOutputFile); + std::cout << "Results written to " << envOutputFile << std::endl; + } + else + { + std::cout << "Resuls not saved." << std::endl; + } + } + else + { + std::cout << "No problems found in the modification template" << std::endl; + } + + std::cout << "Done" << std::endl; + return 0; +} + + +bool processOptions(int argc, char *argv[]) +{ + bool rc = true; + int idx = 1; + std::string optName, optVal; + bool checkDir = false; + + try + { + while (idx < argc) + { + optName = getNextArg(argc, argv, idx++); + + if (optName == "-d" || optName == "--schema-dir") + { + configSchemaDir = getNextArg(argc, argv, idx++) += PATHSEPSTR; + } + + else if (optName == "-p" || optName == "--schema-plugins-dir") + { + configSchemaPluginsDir = getNextArg(argc, argv, idx++) += PATHSEPSTR; + } + + else if (optName == "-m" || optName == "--master-config") + { + masterSchemaFile = getNextArg(argc, argv, idx++); + } + + else if (optName == "-t" || optName == "--template") + { + modTemplateFile = getNextArg(argc, argv, idx++); + } + else if (optName == "-e" || optName == "--env") + { + envFile = getNextArg(argc, argv, idx++); + } + else if (optName == "-o" || optName == "--output") + { + // this is the default if no filename given + envOutputFile = envFile; + if (idx < argc) + { + envOutputFile = getNextArg(argc, argv, idx++); + } + } + else if (optName == "-i" || optName == "--input") + { + std::string assign = getNextArg(argc, argv, idx++); + std::size_t equalPos = assign.find_first_of('='); + if (equalPos != std::string::npos) + { + InputDef input; + input.inputName = assign.substr(0, equalPos); + input.inputValue = assign.substr(equalPos+1); + userInputs.emplace_back(input); + } + else + { + throw CliException("Invalid input variable assignement: " + assign); + } + } + else if (optName == "--inputs") + { + listInputs = true; + } + + } + } + catch(const CliException &e) + { + std::cout << "There was an issue processing option: " << e.what() << std::endl; + rc = false; + } + return rc; +} + + +bool validate() +{ + + if (!dirExists(configSchemaDir)) + { + std::cout << "Schema directory " << configSchemaDir << " does not exist" << std::endl; + return false; + } + + if (!dirExists(configSchemaPluginsDir)) + { + std::cout << "Schema plugins directory " << configSchemaPluginsDir << " does not exist" << std::endl; + return false; + } + + if (!fileExists(configSchemaDir + masterSchemaFile)) + { + std::cout << "The master config file " << masterSchemaFile << " does not exist" << std::endl; + return false; + } + + if (!fileExists(modTemplateFile)) + { + std::cout << "The modification template " << modTemplateFile << " does not exist" << std::endl; + return false; + } + + if (!envFile.empty()) + { + if (!fileExists(envFile)) + { + std::cout << "The environment file " << envFile << " does not exist" << std::endl; + return false; + } + } + + return true; +} + + +std::string getNextArg(int argc, char *argv[], int idx) +{ + if (idx < argc) + { + return std::string(argv[idx]); + } + throw CliException("Arguments exhausted when more expected"); +} + + +bool dirExists(const std::string &dirName) +{ + bool rc = true; + struct stat info; + if (stat(dirName.c_str(), &info) != 0) + { + rc = false; + } + rc = ((info.st_mode&S_IFMT)==S_IFDIR); + return rc; +} + + +bool fileExists(const std::string &fileName) +{ + struct stat info; + return stat(fileName.c_str(), &info) == 0; +} + + +std::vector splitString(const std::string &input, const std::string &delim) +{ + size_t start = 0, end = 0, delimLen = delim.length(); + std::vector list; + + while (end != std::string::npos) + { + end = input.find(delim, start); + std::string item = input.substr(start, (end == std::string::npos) ? std::string::npos : end - start); + if (!item.empty()) + list.push_back(item); + start = ((end > (std::string::npos - delimLen)) ? std::string::npos : end + delimLen); + } + return list; +} diff --git a/configuration/configmgr/configmgrlib/CMakeLists.txt b/configuration/configmgr/configmgrlib/CMakeLists.txt index 79f1132d740..8a3c6fb383f 100644 --- a/configuration/configmgr/configmgrlib/CMakeLists.txt +++ b/configuration/configmgr/configmgrlib/CMakeLists.txt @@ -20,36 +20,39 @@ project ( configmgr ) if (USE_BOOST_REGEX) SET( SRCS - EnvironmentMgr.cpp - XMLEnvironmentMgr.cpp - XMLEnvironmentLoader.cpp - SchemaParser.cpp - SchemaItem.cpp - SchemaValue.cpp - SchemaTypeLimits.cpp - SchemaTypeIntegerLimits.cpp - SchemaTypeStringLimits.cpp - EnvironmentNode.cpp - EnvironmentValue.cpp - XSDComponentParser.cpp - XSDSchemaParser.cpp - XSDValueSetParser.cpp - Status.cpp - EnvironmentEventHandlers.cpp - Utils.cpp - InsertableItem.cpp - ConfigPath.cpp - EnvSupportLib.cpp - mod_template_support/EnvModTemplate.cpp - mod_template_support/Input.cpp - mod_template_support/TemplateException.cpp - mod_template_support/Operation.cpp - mod_template_support/OperationCreateNode.cpp - mod_template_support/Inputs.cpp - mod_template_support/IPAddressRangeInput.cpp - mod_template_support/IPAddressInput.cpp - mod_template_support/HostNameInput.cpp - mod_template_support/OperationNoop.cpp + + EnvironmentMgr.cpp + XMLEnvironmentMgr.cpp + XMLEnvironmentLoader.cpp + SchemaParser.cpp + SchemaItem.cpp + SchemaValue.cpp + SchemaTypeLimits.cpp + SchemaTypeIntegerLimits.cpp + SchemaTypeStringLimits.cpp + EnvironmentNode.cpp + EnvironmentValue.cpp + XSDComponentParser.cpp + XSDSchemaParser.cpp + XSDValueSetParser.cpp + Status.cpp + EnvironmentEventHandlers.cpp + Utils.cpp + InsertableItem.cpp + ConfigPath.cpp + EnvSupportLib.cpp + mod_template_support/EnvModTemplate.cpp + mod_template_support/Variable.cpp + mod_template_support/TemplateException.cpp + mod_template_support/Operation.cpp + mod_template_support/OperationCreateNode.cpp + mod_template_support/OperationFindNode.cpp + mod_template_support/OperationModifyNode.cpp + mod_template_support/Variables.cpp + mod_template_support/IPAddressRangeVariable.cpp + mod_template_support/IPAddressVariable.cpp + mod_template_support/HostNameVariable.cpp + mod_template_support/OperationDeleteNode.cpp ) INCLUDE_DIRECTORIES( diff --git a/configuration/configmgr/configmgrlib/EnvironmentMgr.cpp b/configuration/configmgr/configmgrlib/EnvironmentMgr.cpp index 844224b79c4..08dc30a78ed 100644 --- a/configuration/configmgr/configmgrlib/EnvironmentMgr.cpp +++ b/configuration/configmgr/configmgrlib/EnvironmentMgr.cpp @@ -208,7 +208,7 @@ std::shared_ptr EnvironmentMgr::getNewEnvironmentNode(const std { std::string itemType; std::vector initAttributeValues; - getInitAttributesFromItemType(inputItemType, itemType, initAttributeValues); + getPredefinedAttributeValues(inputItemType, itemType, initAttributeValues); std::shared_ptr pNewCfgItem = findInsertableItem(pParentNode, itemType); if (pNewCfgItem) { @@ -227,19 +227,21 @@ std::shared_ptr EnvironmentMgr::getNewEnvironmentNode(const std std::shared_ptr EnvironmentMgr::addNewEnvironmentNode(const std::string &parentNodeId, const std::string &inputItemType, - std::vector &initAttributes, Status &status) + std::vector &initAttributes, Status &status, bool allowInvalid, bool forceCreate) { std::shared_ptr pNewNode; std::shared_ptr pParentNode = findEnvironmentNodeById(parentNodeId); if (pParentNode) { std::string itemType; - std::vector initAttributeValues; - getInitAttributesFromItemType(inputItemType, itemType, initAttributeValues); + std::vector attributeValues; + getPredefinedAttributeValues(inputItemType, itemType, attributeValues); std::shared_ptr pNewCfgItem = findInsertableItem(pParentNode, itemType); if (pNewCfgItem) { - pNewNode = addNewEnvironmentNode(pParentNode, pNewCfgItem, initAttributes, status); + // concatenate input attribute values with any predefined values. + attributeValues.insert( attributeValues.end(), initAttributes.begin(), initAttributes.end() ); + pNewNode = addNewEnvironmentNode(pParentNode, pNewCfgItem, attributeValues, status, allowInvalid, forceCreate); if (pNewNode == nullptr) { status.addMsg(statusMsg::error, "Unable to create new node for itemType: " + inputItemType); @@ -263,7 +265,7 @@ std::shared_ptr EnvironmentMgr::addNewEnvironmentNode(const std std::shared_ptr EnvironmentMgr::addNewEnvironmentNode(const std::shared_ptr &pParentNode, const std::shared_ptr &pCfgItem, - std::vector &initAttributes, Status &status) + std::vector &initAttributes, Status &status, bool allowInvalid, bool forceCreate) { std::shared_ptr pNewEnvNode; @@ -274,7 +276,7 @@ std::shared_ptr EnvironmentMgr::addNewEnvironmentNode(const std addPath(pNewEnvNode); pNewEnvNode->initialize(); - pNewEnvNode->setAttributeValues(initAttributes, status, false, false); + pNewEnvNode->setAttributeValues(initAttributes, status, allowInvalid, forceCreate); pParentNode->addChild(pNewEnvNode); // @@ -309,22 +311,25 @@ std::shared_ptr EnvironmentMgr::addNewEnvironmentNode(const std std::shared_ptr EnvironmentMgr::findInsertableItem(const std::shared_ptr &pNode, const std::string &itemType) const { - std::shared_ptr pItem; + // todo -should there be an environment strict mode that forces schema compliance? + std::shared_ptr pSchemaItem = std::make_shared(itemType, "default"); // default item if none found std::vector insertableItems; pNode->getInsertableItems(insertableItems); for (auto &pInsertableIt: insertableItems) { if (pInsertableIt.m_pSchemaItem->getItemType() == itemType) { - pItem = pInsertableIt.m_pSchemaItem; + pSchemaItem = pInsertableIt.m_pSchemaItem; break; // we found the insertable item we wanted, so time to get out } } - return pItem; + + return pSchemaItem; } -void EnvironmentMgr::getInitAttributesFromItemType(const std::string &inputItemType, std::string &itemType, std::vector &initAttributes) const +void EnvironmentMgr::getPredefinedAttributeValues(const std::string &inputItemType, std::string &itemType, + std::vector &initAttributes) const { // // In case nothing specifed diff --git a/configuration/configmgr/configmgrlib/EnvironmentMgr.hpp b/configuration/configmgr/configmgrlib/EnvironmentMgr.hpp index 8b84f71c65b..8f4fccd1b57 100644 --- a/configuration/configmgr/configmgrlib/EnvironmentMgr.hpp +++ b/configuration/configmgr/configmgrlib/EnvironmentMgr.hpp @@ -54,8 +54,10 @@ class DECL_EXPORT EnvironmentMgr bool loadEnvironment(const std::string &qualifiedFilename); std::shared_ptr findEnvironmentNodeById(const std::string &nodeId) const; std::shared_ptr getNewEnvironmentNode(const std::string &parentNodeId, const std::string &inputItem, Status &status) const; - std::shared_ptr addNewEnvironmentNode(const std::string &parentNodeId, const std::string &configType, std::vector &initAttributes, Status &status); - std::shared_ptr addNewEnvironmentNode(const std::shared_ptr &pParentNode, const std::shared_ptr &pNewCfgItem, std::vector &initAttributes, Status &status); + std::shared_ptr addNewEnvironmentNode(const std::string &parentNodeId, const std::string &configType, + std::vector &initAttributes, Status &status, bool allowInvalid=false, bool forceCreate=false); + std::shared_ptr addNewEnvironmentNode(const std::shared_ptr &pParentNode, const std::shared_ptr &pNewCfgItem, + std::vector &initAttributes, Status &status, bool allowInvalid=false, bool forceCreate=false); bool removeEnvironmentNode(const std::string &nodeId); bool saveEnvironment(const std::string &qualifiedFilename); void discardEnvironment() { m_pRootNode = nullptr; m_nodeIds.clear();} @@ -76,7 +78,8 @@ class DECL_EXPORT EnvironmentMgr void assignNodeIds(const std::shared_ptr &pNode); void insertExtraEnvironmentData(std::shared_ptr pNode); std::shared_ptr findInsertableItem(const std::shared_ptr &pNode, const std::string &itemType) const; - void getInitAttributesFromItemType(const std::string &inputItemType, std::string &itemType, std::vector &initAttributes) const; + void getPredefinedAttributeValues(const std::string &inputItemType, std::string &itemType, + std::vector &initAttributes) const; protected: diff --git a/configuration/configmgr/configmgrlib/EnvironmentNode.cpp b/configuration/configmgr/configmgrlib/EnvironmentNode.cpp index a8b156dd6ae..ab8fd82b89b 100644 --- a/configuration/configmgr/configmgrlib/EnvironmentNode.cpp +++ b/configuration/configmgr/configmgrlib/EnvironmentNode.cpp @@ -160,7 +160,16 @@ void EnvironmentNode::setAttributeValue(const std::string &attrName, const std:: if (pEnvValue) { - pEnvValue->setValue(value, &status, allowInvalid); + // + // If the value is not empty, set the attribute to the new value. If empty, delete the attribute + if (!value.empty()) + { + pEnvValue->setValue(value, &status, allowInvalid); + } + else + { + m_attributes.erase(it); + } } else { @@ -385,10 +394,17 @@ void EnvironmentNode::doFetchNodes(ConfigPath &configPath, std::vectorisRoot()) { - std::shared_ptr pRoot = getRoot(); + std::shared_ptr pRoot = getRoot(); if (pRoot->getName() == pPathItem->getElementName()) { - pRoot->doFetchNodes(configPath, nodes); + if (configPath.isPathRemaining()) + { + pRoot->doFetchNodes(configPath, nodes); + } + else + { + nodes.emplace_back(pRoot); + } } else { @@ -475,12 +491,12 @@ void EnvironmentNode::doFetchNodes(ConfigPath &configPath, std::vector EnvironmentNode::getRoot() const +std::shared_ptr EnvironmentNode::getRoot() const { - if (!m_pParent.expired()) + std::shared_ptr pParent = m_pParent.lock(); + if (pParent) { - return m_pParent.lock()->getRoot(); + return pParent->getRoot(); } - std::shared_ptr ptr = shared_from_this(); - return ptr; + return std::const_pointer_cast(shared_from_this()); } diff --git a/configuration/configmgr/configmgrlib/EnvironmentNode.hpp b/configuration/configmgr/configmgrlib/EnvironmentNode.hpp index 2ab3f237c03..d67f01e01b4 100644 --- a/configuration/configmgr/configmgrlib/EnvironmentNode.hpp +++ b/configuration/configmgr/configmgrlib/EnvironmentNode.hpp @@ -34,7 +34,7 @@ class DECL_EXPORT EnvironmentNode : public std::enable_shared_from_this &pCfgItem, const std::string &elemName, const std::shared_ptr &pParent = nullptr) : + EnvironmentNode(const std::shared_ptr &pCfgItem, const std::string &elemName, const std::shared_ptr &pParent = std::shared_ptr()) : m_pSchemaItem(pCfgItem), m_name(elemName), m_pParent(pParent) { } ~EnvironmentNode() { } const std::string &getName() const { return m_name; } @@ -68,7 +68,7 @@ class DECL_EXPORT EnvironmentNode : public std::enable_shared_from_this &items) const; void initialize(); void fetchNodes(const std::string &path, std::vector> &nodes) const; - std::shared_ptr getRoot() const; + std::shared_ptr getRoot() const; void addEnvironmentInsertData(const std::string &envData) { m_insertData = envData; } const std::string &getEnvironmentInsertData() const { return m_insertData; } void clearEnvironmentInsertData() { m_insertData.clear(); } diff --git a/configuration/configmgr/configmgrlib/EnvironmentValue.cpp b/configuration/configmgr/configmgrlib/EnvironmentValue.cpp index 23f144464d1..8f77be0ec47 100644 --- a/configuration/configmgr/configmgrlib/EnvironmentValue.cpp +++ b/configuration/configmgr/configmgrlib/EnvironmentValue.cpp @@ -149,7 +149,7 @@ void EnvironmentValue::initialize() // until unique. prefix_ uses an _ between value and the number (value_3) // prefix# - always appends a number thus creating an incrementing unique value starting at 1 // (value1, value2, value3, ...) - if (type == "prefix" || type=="prefix_" || "prefix#") + if (type == "prefix" || type == "prefix_" || type == "prefix#") { std::string connector = (type == "prefix_") ? "_" : ""; std::string newName; diff --git a/configuration/configmgr/configmgrlib/XMLEnvironmentLoader.cpp b/configuration/configmgr/configmgrlib/XMLEnvironmentLoader.cpp index d9324d7994a..8fa225e85d2 100644 --- a/configuration/configmgr/configmgrlib/XMLEnvironmentLoader.cpp +++ b/configuration/configmgr/configmgrlib/XMLEnvironmentLoader.cpp @@ -99,7 +99,7 @@ void XMLEnvironmentLoader::parse(const pt::ptree &envTree, const std::shared_ptr } // - // Find elements in environment tree cooresponding to this config item, then parse each + // Find elements in environment tree corresponding to this config item, then parse each for (auto it = envTree.begin(); it != envTree.end(); ++it) { std::string elemName = it->first; diff --git a/configuration/configmgr/configmgrlib/XMLEnvironmentMgr.cpp b/configuration/configmgr/configmgrlib/XMLEnvironmentMgr.cpp index eb4cd9c4ef8..717720291cd 100644 --- a/configuration/configmgr/configmgrlib/XMLEnvironmentMgr.cpp +++ b/configuration/configmgr/configmgrlib/XMLEnvironmentMgr.cpp @@ -19,6 +19,7 @@ #include "XSDSchemaParser.hpp" #include "XMLEnvironmentLoader.hpp" #include "Exceptions.hpp" +#include "boost/version.hpp" bool XMLEnvironmentMgr::createParser() @@ -54,7 +55,15 @@ bool XMLEnvironmentMgr::save(std::ostream &out) pt::ptree envTree, topTree; serialize(envTree, m_pRootNode); topTree.add_child("Environment", envTree); - pt::write_xml(out, topTree); +// boost::property_tree::xml_writer_settings settings; + //pt::write_xml(out, topTree, pt::xml_parser::xml_writer_make_settings(' ', 4)); +#if BOOST_VERSION >= 105800 + pt::write_xml(out, topTree, pt::xml_parser::xml_writer_make_settings(' ', 4)); +#else + const char * myIndent = " "; + pt::write_xml(out, topTree, pt::xml_parser::xml_writer_make_settings(*myIndent , 4)); +#endif + } catch (const std::exception &e) { diff --git a/configuration/configmgr/configmgrlib/XMLEnvironmentMgr.hpp b/configuration/configmgr/configmgrlib/XMLEnvironmentMgr.hpp index ccfc37c5ad6..802cb0cb399 100644 --- a/configuration/configmgr/configmgrlib/XMLEnvironmentMgr.hpp +++ b/configuration/configmgr/configmgrlib/XMLEnvironmentMgr.hpp @@ -22,6 +22,7 @@ #include #include +#include namespace pt = boost::property_tree; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/EnvModTemplate.cpp b/configuration/configmgr/configmgrlib/mod_template_support/EnvModTemplate.cpp index b335010f7c7..dcf8626248a 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/EnvModTemplate.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/EnvModTemplate.cpp @@ -18,23 +18,29 @@ #include "EnvModTemplate.hpp" #include "rapidjson/error/en.h" #include "TemplateException.hpp" +#include "TemplateExecutionException.hpp" #include "OperationCreateNode.hpp" -#include "OperationNoop.hpp" +#include "OperationFindNode.hpp" +#include "OperationModifyNode.hpp" +#include "OperationDeleteNode.hpp" #include #include #include "Utils.hpp" +// +// Good online resource for validation of modification templates is https://www.jsonschemavalidator.net/ +// Load the schecma (ModTemplateSchema.json) in the left window and the modification template in the right. -EnvModTemplate::EnvModTemplate(EnvironmentMgr *pEnvMgr, const std::string &fqSchemaFile) : m_pEnvMgr(pEnvMgr), m_pTemplate(nullptr), m_pSchema(nullptr) +EnvModTemplate::EnvModTemplate(EnvironmentMgr *pEnvMgr, const std::string &schemaFile) : m_pEnvMgr(pEnvMgr), m_pTemplate(nullptr), m_pSchema(nullptr) { if (m_pEnvMgr == nullptr) { - throw(TemplateException("Environment Modification Template requires a valid Environment Manager to be initialized")); + throw(TemplateException("Environment Modification Template requires a valid Environment Manager")); } // // Load and compile the schema document - std::ifstream jsonFile(fqSchemaFile); + std::ifstream jsonFile(schemaFile); rapidjson::IStreamWrapper jsonStream(jsonFile); rapidjson::Document sd; if (sd.ParseStream(jsonStream).HasParseError()) @@ -86,11 +92,10 @@ void EnvModTemplate::loadTemplateFromJson(const std::string &templateJson) void EnvModTemplate::loadTemplate(rapidjson::IStreamWrapper &stream) { - // // Cleanup anything that may be laying around. releaseTemplate(); - m_inputs.clear(); + m_variables.clear(); m_pTemplate = new rapidjson::Document(); @@ -117,9 +122,7 @@ void EnvModTemplate::loadTemplate(rapidjson::IStreamWrapper &stream) sb.Clear(); validator.GetInvalidDocumentPointer().StringifyUriFragment(sb); msg += "Invalid document: " + std::string(sb.GetString()); - TemplateException te(msg); - te.setInvalidTemplate(true); - throw te; + throw TemplateException(msg, true); } } @@ -136,10 +139,10 @@ void EnvModTemplate::parseTemplate() // // Inputs - if (m_pTemplate->HasMember("inputs")) + if (m_pTemplate->HasMember("variables")) { - const rapidjson::Value &inputs = (*m_pTemplate)["inputs"]; - parseInputs(inputs); + const rapidjson::Value &variables = (*m_pTemplate)["variables"]; + parseVariables(variables); } // @@ -148,59 +151,118 @@ void EnvModTemplate::parseTemplate() } -void EnvModTemplate::parseInputs(const rapidjson::Value &inputs) +void EnvModTemplate::parseVariables(const rapidjson::Value &variables) { - for (auto &inputDef: inputs.GetArray()) + for (auto &varDef: variables.GetArray()) { - parseInput(inputDef); + parseVariable(varDef); } } -void EnvModTemplate::parseInput(const rapidjson::Value &input) +void EnvModTemplate::parseVariable(const rapidjson::Value &varValue) { rapidjson::Value::ConstMemberIterator it; - std::shared_ptr pInput; + std::shared_ptr pVariable; // // input name, make sure not a duplicate - std::string inputName(input.FindMember("name")->value.GetString()); - std::string type(input.FindMember("type")->value.GetString()); - pInput = inputValueFactory(type, inputName); + std::string varName(varValue.FindMember("name")->value.GetString()); + std::string type(varValue.FindMember("type")->value.GetString()); + pVariable = variableFactory(type, varName); // // Get and set the rest of the input values - it = input.FindMember("prompt"); - if (it != input.MemberEnd()) - pInput->setUserPrompt(it->value.GetString()); + it = varValue.FindMember("prompt"); + if (it != varValue.MemberEnd()) + pVariable->m_userPrompt = it->value.GetString(); else - pInput->setUserPrompt("Input value for " + inputName); + pVariable->m_userPrompt = "Input value for " + varName; - it = input.FindMember("description"); - if (it != input.MemberEnd()) pInput->setDescription(it->value.GetString()); + it = varValue.FindMember("description"); + if (it != varValue.MemberEnd()) pVariable->m_description = it->value.GetString(); - it = input.FindMember("tooltip"); - if (it != input.MemberEnd()) pInput->setTooltip(it->value.GetString()); + it = varValue.FindMember("values"); + if (it != varValue.MemberEnd()) + { + for (auto &val: it->value.GetArray()) + { + pVariable->addValue(trim(val.GetString())); + } + } - it = input.FindMember("value"); - if (it != input.MemberEnd()) pInput->setValue(trim(it->value.GetString())); + it = varValue.FindMember("prepared_value"); + if (it != varValue.MemberEnd()) pVariable->m_preparedValue = trim(it->value.GetString()); - it = input.FindMember("prepared_value"); - if (it != input.MemberEnd()) pInput->setPreparedValue(trim(it->value.GetString())); + it = varValue.FindMember("user_input"); + if (it != varValue.MemberEnd()) pVariable->m_userInput = it->value.GetBool(); - m_inputs.add(pInput); + m_variables.add(pVariable); } -std::shared_ptr EnvModTemplate::getInput(const std::string &name, bool throwIfNotFound) const +std::shared_ptr EnvModTemplate::getVariable(const std::string &name, bool throwIfNotFound) const { - return m_inputs.getInput(name, throwIfNotFound); + return m_variables.getVariable(name, throwIfNotFound); } -std::vector> EnvModTemplate::getInputs(int phase) const +std::vector> EnvModTemplate::getVariables(bool userInputOnly) const { - return m_inputs.all(phase); + std::vector> variables; + if (!userInputOnly) + { + variables.insert( variables.end(), m_variables.all().begin(), m_variables.all().end() ); + } + else + { + auto allVars = m_variables.all(); + for (auto &varIt: allVars) + { + if (varIt->m_userInput) + variables.emplace_back(varIt); + } + } + + return variables; +} + + +void EnvModTemplate::assignVariablesFromFile(const std::string &filepath) +{ + std::ifstream jsonFile(filepath); + rapidjson::IStreamWrapper jsonStream(jsonFile); + + // + // Format: + // { + // "variable-name" : [ "value1", "value2" , ..., "valuen"], + // ... + // } + + rapidjson::Document inputJson; + + // + // Parse and make sure it's valid JSON + inputJson.ParseStream(jsonStream); + if (inputJson.HasParseError()) + { + throw(TemplateException(&inputJson)); + } + + // + // go through all the inputs and assign values + for (auto itr = inputJson.MemberBegin(); + itr != inputJson.MemberEnd(); ++itr) + { + std::string inputName = itr->name.GetString(); + auto pInput = m_variables.getVariable(inputName); + auto valueArray = itr->value.GetArray(); + for (auto &val: valueArray) + { + pInput->addValue(val.GetString()); + } + } } @@ -220,49 +282,99 @@ void EnvModTemplate::parseOperation(const rapidjson::Value &operation) if (action == "create_node") { - pOp = parseCreateNodeOperation(operation); + pOp = std::make_shared(); } - else if (action == "noop" || action == "modify_node") + else if (action == "modify_node") { - pOp = parseOperationNoop(operation); + pOp = std::make_shared(); + } + else if (action == "find_node") + { + pOp = std::make_shared(); + } + else if (action == "delete_node") + { + pOp = std::make_shared(); } else { throw TemplateException("Unsupported operation '" + action + "' found", false); } + // + // Get the parent based on presence of key (schema validates presence of one of these) + auto it = operation.FindMember("parent_path"); + if (it != operation.MemberEnd()) + { + pOp->m_path = trim(it->value.GetString()); + } + else + { + it = operation.FindMember("parent_nodeid"); + pOp->m_parentNodeId = trim(it->value.GetString()); + } + + // + // Parse the data section + auto dataIt = operation.FindMember("data"); + if (dataIt != operation.MemberEnd()) + { + parseOperationCommonData(dataIt->value, pOp); + + // + // Parse specific operation type values + if (action == "create_node") + { + std::shared_ptr pCreateOp = std::dynamic_pointer_cast(pOp); + pCreateOp->m_nodeType = dataIt->value.FindMember("node_type")->value.GetString(); + } + else if (action == "find_node") + { + it = dataIt->value.FindMember("create"); + if (it != dataIt->value.MemberEnd()) + { + std::shared_ptr pFindOp = std::dynamic_pointer_cast(pOp); + pFindOp->m_createIfNotFound = it->value.GetBool(); + } + } + } + m_operations.emplace_back(pOp); } -void EnvModTemplate::parseOperationCommon(const rapidjson::Value &operation, std::shared_ptr pOp) +void EnvModTemplate::parseOperationCommonData(const rapidjson::Value &operationData, std::shared_ptr pOp) { rapidjson::Value::ConstMemberIterator it; + auto dataObj = operationData.GetObject(); + // - // Either a node ID or a path - it = operation.FindMember("parent_nodeid"); - if (it != operation.MemberEnd()) - pOp->setParentNodeId(it->value.GetString()); - else - pOp->setPath(operation.FindMember("path")->value.GetString()); + // Get the count (optional, default is 1) + it = dataObj.FindMember("count"); + if (it != dataObj.MemberEnd()) + pOp->m_count = trim(it->value.GetString()); // - // Get the count - it = operation.FindMember("count"); - if (it != operation.MemberEnd()) - pOp->setCount(trim(it->value.GetString())); + // Get the starting index (optional, for windowing into a range of values) + it = dataObj.FindMember("start_index"); + if (it != dataObj.MemberEnd()) + pOp->m_startIndex = trim(it->value.GetString()); // - // Get the starting index (for windowing into a range of values) - it = operation.FindMember("start_index"); - if (it != operation.MemberEnd()) - pOp->setStartIndex(trim(it->value.GetString())); + // Save node id + rapidjson::Value::ConstMemberIterator saveInfoIt = dataObj.FindMember("save_nodeid"); + if (saveInfoIt != dataObj.MemberEnd()) + { + pOp->m_saveNodeIdName = saveInfoIt->value.GetObject().FindMember("save_name")->value.GetString(); + it = saveInfoIt->value.GetObject().FindMember("duplicate_ok"); + if (it != saveInfoIt->value.MemberEnd()) pOp->m_duplicateSaveNodeIdInputOk = it->value.GetBool(); + } // // Get the attributes (if any) - it = operation.FindMember("attributes"); - if (it != operation.MemberEnd()) + it = dataObj.FindMember("attributes"); + if (it != dataObj.MemberEnd()) { rapidjson::Value::ConstMemberIterator attrValueIt; std::string attrName, attrValue; @@ -272,8 +384,12 @@ void EnvModTemplate::parseOperationCommon(const rapidjson::Value &operation, std // Get the attribute name and value. Note that these are required by the schema so the template would // have never loaded if either was missing. attrName = trim(attr.FindMember("name")->value.GetString()); - attrValue = trim(attr.FindMember("value")->value.GetString()); + auto valueIt = attr.FindMember("value"); + if (valueIt != attr.MemberEnd()) + { + attrValue = trim(attr.FindMember("value")->value.GetString()); + } modAttribute newAttribute(attrName, attrValue); attrValueIt = attr.FindMember("start_index"); @@ -282,62 +398,56 @@ void EnvModTemplate::parseOperationCommon(const rapidjson::Value &operation, std attrValueIt = attr.FindMember("do_not_set"); if (attrValueIt != attr.MemberEnd()) newAttribute.doNotSet = attrValueIt->value.GetBool(); - attrValueIt = attr.FindMember("save_value"); - if (attrValueIt != attr.MemberEnd()) newAttribute.saveValue = trim(attrValueIt->value.GetString()); - + saveInfoIt = attr.FindMember("save_value"); + if (saveInfoIt != attr.MemberEnd()) + { + newAttribute.saveValue = saveInfoIt->value.GetObject().FindMember("save_name")->value.GetString(); + attrValueIt = saveInfoIt->value.GetObject().FindMember("duplicate_ok"); + if (attrValueIt != saveInfoIt->value.MemberEnd()) + newAttribute.duplicateSaveValueOk = attrValueIt->value.GetBool(); + } pOp->addAttribute(newAttribute); } } -} - - -std::shared_ptr EnvModTemplate::parseCreateNodeOperation(const rapidjson::Value &operation) -{ - rapidjson::Value::ConstMemberIterator it; - std::shared_ptr pOp = std::make_shared(); - - parseOperationCommon(operation, pOp); - - // set the node type (schema ensures its presence) - pOp->setNodeType(operation.FindMember("node_type")->value.GetString()); // - // Get the potential name underwhich the newly created node ID will be saved - it = operation.FindMember("save_nodeid"); - if (it != operation.MemberEnd()) - pOp->setSaveNodeIdName(it->value.GetString()); - - return pOp; + // Throw on empty is a flag that will force an exception to be thrown during execution if no + // nodes are found to modify. + it = dataObj.FindMember("error_if_not_found"); + if (it != dataObj.MemberEnd()) + pOp->m_throwOnEmpty = it->value.GetBool(); } -std::shared_ptr EnvModTemplate::parseOperationNoop(const rapidjson::Value &operation) +void EnvModTemplate::execute() { - rapidjson::Value::ConstMemberIterator it; - std::shared_ptr pOp = std::make_shared(); - parseOperationCommon(operation, pOp); - - it = operation.FindMember("count"); - if (it != operation.MemberEnd()) - pOp->setCount(it->value.GetString()); - - return pOp; -} - - -bool EnvModTemplate::execute() -{ - bool rc = false; - - // - // Do final prep on inputs. This may set values for inputs that are dependent on previously set inputs - m_inputs.prepare(); + try + { + // + // Do final prep on inputs. This may set values for inputs that are dependent on previously set inputs + m_variables.prepare(); + } + catch (TemplateExecutionException &te) + { + te.setStep("Variable preparation"); + throw; + } + unsigned opNum = 1; for (auto &pOp: m_operations) { - pOp->execute(m_pEnvMgr, &m_inputs); + try + { + pOp->execute(m_pEnvMgr, &m_variables); + } + catch (TemplateExecutionException &te) + { + // + // Set the operation step number and rethrow so user can tell what step caused the problem + te.setStep("Operation step " + std::to_string(opNum)); + throw; + } + ++opNum; } - - return rc; } diff --git a/configuration/configmgr/configmgrlib/mod_template_support/EnvModTemplate.hpp b/configuration/configmgr/configmgrlib/mod_template_support/EnvModTemplate.hpp index 865fc6412a3..63d5f22fc61 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/EnvModTemplate.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/EnvModTemplate.hpp @@ -24,8 +24,8 @@ #include "rapidjson/istreamwrapper.h" #include "TemplateException.hpp" #include -#include "Input.hpp" -#include "Inputs.hpp" +#include "Variable.hpp" +#include "Variables.hpp" #include "Operation.hpp" #include #include @@ -36,14 +36,15 @@ class EnvModTemplate public: - EnvModTemplate(EnvironmentMgr *pEnvMgr, const std::string &fqSchemaFile); + EnvModTemplate(EnvironmentMgr *pEnvMgr, const std::string &schemaFile); ~EnvModTemplate(); void loadTemplateFromJson(const std::string &templateJson); void loadTemplateFromFile(const std::string &fqTemplateFile); - std::shared_ptr getInput(const std::string &name, bool throwIfNotFound = true) const; - std::vector> getInputs(int phase = 0) const; - bool execute(); + std::shared_ptr getVariable(const std::string &name, bool throwIfNotFound = true) const; + std::vector> getVariables(bool userInputOnly = false) const; + void assignVariablesFromFile(const std::string &filepath); + void execute(); protected: @@ -51,13 +52,11 @@ class EnvModTemplate void releaseTemplate(); void loadTemplate(rapidjson::IStreamWrapper &stream); void parseTemplate(); - void parseInputs(const rapidjson::Value &inputs); - void parseInput(const rapidjson::Value &input); + void parseVariables(const rapidjson::Value &variables); + void parseVariable(const rapidjson::Value &varValue); void parseOperations(const rapidjson::Value &operations); void parseOperation(const rapidjson::Value &operation); - void parseOperationCommon(const rapidjson::Value &operation, std::shared_ptr pOp); - std::shared_ptr parseCreateNodeOperation(const rapidjson::Value &operation); - std::shared_ptr parseOperationNoop(const rapidjson::Value &operation); + void parseOperationCommonData(const rapidjson::Value &operationData, std::shared_ptr pOp); protected: @@ -65,7 +64,7 @@ class EnvModTemplate rapidjson::SchemaDocument *m_pSchema; rapidjson::Document *m_pTemplate; // same as GenericDocument > EnvironmentMgr *m_pEnvMgr; - Inputs m_inputs; + Variables m_variables; std::vector> m_operations; }; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/HostNameInput.cpp b/configuration/configmgr/configmgrlib/mod_template_support/HostNameVariable.cpp similarity index 91% rename from configuration/configmgr/configmgrlib/mod_template_support/HostNameInput.cpp rename to configuration/configmgr/configmgrlib/mod_template_support/HostNameVariable.cpp index 51459997586..26b6e739c11 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/HostNameInput.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/HostNameVariable.cpp @@ -15,11 +15,11 @@ limitations under the License. ############################################################################## */ -#include "HostNameInput.hpp" +#include "HostNameVariable.hpp" #include "TemplateException.hpp" #include "Utils.hpp" -void HostNameInput::setValue(const std::string &value) { +void HostNameVariable::addValue(const std::string &value) { std::string hostname = trim(value); bool isValid = true; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/HostNameInput.hpp b/configuration/configmgr/configmgrlib/mod_template_support/HostNameVariable.hpp similarity index 79% rename from configuration/configmgr/configmgrlib/mod_template_support/HostNameInput.hpp rename to configuration/configmgr/configmgrlib/mod_template_support/HostNameVariable.hpp index 3719b5a04e4..088dd77595d 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/HostNameInput.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/HostNameVariable.hpp @@ -18,16 +18,15 @@ #ifndef HPCCSYSTEMS_PLATFORM_HOSTNAMEINPUT_HPP #define HPCCSYSTEMS_PLATFORM_HOSTNAMEINPUT_HPP -#include "Input.hpp" -#include +#include "Variable.hpp" #include -class HostNameInput : public Input +class HostNameVariable : public Variable { public: - HostNameInput() = default; - ~HostNameInput() = default; - void setValue(const std::string &value); + explicit HostNameVariable(const std::string &name) : Variable(name) {} + ~HostNameVariable() override = default; + void addValue(const std::string &value) override; }; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeInput.cpp b/configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeVariable.cpp similarity index 97% rename from configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeInput.cpp rename to configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeVariable.cpp index c23245880a8..0a1a60190e5 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeInput.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeVariable.cpp @@ -15,16 +15,16 @@ limitations under the License. ############################################################################## */ -#include "IPAddressRangeInput.hpp" +#include "IPAddressRangeVariable.hpp" #include "TemplateException.hpp" #include "Utils.hpp" -void IPAddressRangeInput::setValue(const std::string &range) +void IPAddressRangeVariable::addValue(const std::string &range) { // // Formats accepted: // iplist = ip[;ip] - // ip = a.b.c.d | a.b.c.g-h + // ip = a.b.c.d | a.b.c.g-h | a.b.c.d*10 m_values.clear(); std::vector ipDefs = splitString(range, ";"); diff --git a/configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeInput.hpp b/configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeVariable.hpp similarity index 77% rename from configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeInput.hpp rename to configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeVariable.hpp index cc2b8a6cded..7685f69661d 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeInput.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/IPAddressRangeVariable.hpp @@ -18,16 +18,15 @@ #ifndef HPCCSYSTEMS_PLATFORM_IPRANGE_HPP #define HPCCSYSTEMS_PLATFORM_IPRANGE_HPP -#include "Input.hpp" -#include +#include "Variable.hpp" #include -class IPAddressRangeInput : public Input +class IPAddressRangeVariable : public Variable { public: - IPAddressRangeInput() = default; - ~IPAddressRangeInput() = default; - void setValue(const std::string &value); + explicit IPAddressRangeVariable(const std::string &name) : Variable(name) {} + ~IPAddressRangeVariable() override = default; + void addValue(const std::string &value) override; }; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/IPAddressInput.cpp b/configuration/configmgr/configmgrlib/mod_template_support/IPAddressVariable.cpp similarity index 94% rename from configuration/configmgr/configmgrlib/mod_template_support/IPAddressInput.cpp rename to configuration/configmgr/configmgrlib/mod_template_support/IPAddressVariable.cpp index c3f00148a3e..abf92332bbe 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/IPAddressInput.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/IPAddressVariable.cpp @@ -15,11 +15,11 @@ limitations under the License. ############################################################################## */ -#include "IPAddressInput.hpp" +#include "IPAddressVariable.hpp" #include "TemplateException.hpp" #include "Utils.hpp" -void IPAddressInput::setValue(const std::string &value) { +void IPAddressVariable::addValue(const std::string &value) { std::string ipAddr = trim(value); bool isValid = true; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/IPAddressInput.hpp b/configuration/configmgr/configmgrlib/mod_template_support/IPAddressVariable.hpp similarity index 79% rename from configuration/configmgr/configmgrlib/mod_template_support/IPAddressInput.hpp rename to configuration/configmgr/configmgrlib/mod_template_support/IPAddressVariable.hpp index 9b8f76e8634..86061a8e68d 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/IPAddressInput.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/IPAddressVariable.hpp @@ -18,16 +18,15 @@ #ifndef HPCCSYSTEMS_PLATFORM_IPADDRESS_HPP #define HPCCSYSTEMS_PLATFORM_IPADDRESS_HPP -#include "Input.hpp" -#include +#include "Variable.hpp" #include -class IPAddressInput : public Input +class IPAddressVariable : public Variable { public: - IPAddressInput() = default; - ~IPAddressInput() = default; - void setValue(const std::string &value); + explicit IPAddressVariable(const std::string &name) : Variable(name) {} + ~IPAddressVariable() override = default; + void addValue(const std::string &value) override; }; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/Operation.cpp b/configuration/configmgr/configmgrlib/mod_template_support/Operation.cpp index a4ca6b06fe8..8e988fb75c6 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/Operation.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/Operation.cpp @@ -16,7 +16,9 @@ ############################################################################## */ #include "Operation.hpp" -#include "TemplateException.hpp" +#include "TemplateExecutionException.hpp" +#include "EnvironmentNode.hpp" +#include "EnvironmentValue.hpp" void Operation::addAttribute(modAttribute &newAttribute) @@ -25,7 +27,7 @@ void Operation::addAttribute(modAttribute &newAttribute) } -bool Operation::execute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) +bool Operation::execute(EnvironmentMgr *pEnvMgr, Variables *pInputs) { bool rc = true; size_t count, startIndex; @@ -38,7 +40,7 @@ bool Operation::execute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) try { count = std::stoul(countStr); } catch (...) { - throw TemplateException("Non-numeric count found for count", false); + throw TemplateExecutionException("Non-numeric count found for count"); } // @@ -47,7 +49,7 @@ bool Operation::execute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) try { startIndex = std::stoul(startIdxStr); } catch (...) { - throw TemplateException("Non-numeric count found for start index", false); + throw TemplateExecutionException("Non-numeric count found for start index"); } // @@ -62,7 +64,7 @@ bool Operation::execute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) } -void Operation::assignAttributeCookedValues(Inputs *pInputs) +void Operation::assignAttributeCookedValues(Variables *pInputs) { // // go through the ones defined by the operation and set each (by name) @@ -71,3 +73,85 @@ void Operation::assignAttributeCookedValues(Inputs *pInputs) attr.cookedValue = pInputs->doValueSubstitution(attr.value); } } + + +void Operation::getParentNodeIds(EnvironmentMgr *pEnvMgr, Variables *pInputs) +{ + // + // Find the parent node(s). Either was input, or based on a path, which may match more than one node + if (!m_parentNodeId.empty()) + { + std::shared_ptr pInput = pInputs->getVariable(m_parentNodeId); + if (pInput) + { + std::size_t numIds = pInput->getNumValues(); + for (std::size_t idx = 0; idx < numIds; ++idx) + { + m_parentNodeIds.emplace_back(pInputs->doValueSubstitution(pInput->getValue(idx))); + } + } + else + { + m_parentNodeIds.emplace_back(pInputs->doValueSubstitution(m_parentNodeId)); + } + } + else + { + std::vector> envNodes; + std::string path = pInputs->doValueSubstitution(m_path); + pEnvMgr->fetchNodes(path, envNodes); + for (auto &envNode: envNodes) + m_parentNodeIds.emplace_back(envNode->getId()); + } +} + + +std::shared_ptr Operation::createInput(std::string inputName, const std::string &inputType, Variables *pInputs, bool existingOk) +{ + std::shared_ptr pInput; + + pInput = pInputs->getVariable(inputName, false); + + if (!pInput) + { + pInput = variableFactory(inputType, inputName); + pInputs->add(pInput); + } + else if (!existingOk) + { + std::string msg = "Attempt to create input value that already exists: " + inputName; + throw TemplateExecutionException(msg); + } + return pInput; +} + + +void Operation::createAttributeSaveInputs(Variables *pInputs) +{ + for (auto &attr: m_attributes) + { + // + // If this is a saved attribute value, make sure the input exists + if (!attr.saveValue.empty()) + { + createInput(attr.saveValue, "string", pInputs, attr.duplicateSaveValueOk); + } + } +} + + +void Operation::saveAttributeValues(Variables *pInputs, const std::shared_ptr &pEnvNode) +{ + for (auto &attr: m_attributes) + { + if (!attr.saveValue.empty()) + { + std::shared_ptr pInput = pInputs->getVariable(attr.saveValue); + auto pAttr = pEnvNode->getAttribute(attr.name); + if (pAttr) + { + pInput->addValue(pAttr->getValue()); + } + } + } +} diff --git a/configuration/configmgr/configmgrlib/mod_template_support/Operation.hpp b/configuration/configmgr/configmgrlib/mod_template_support/Operation.hpp index 158a3923503..26c768a85b1 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/Operation.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/Operation.hpp @@ -18,16 +18,16 @@ #ifndef HPCCSYSTEMS_PLATFORM_OPERATION_HPP #define HPCCSYSTEMS_PLATFORM_OPERATION_HPP -#include "Input.hpp" -#include "Inputs.hpp" +#include "Variable.hpp" +#include "Variables.hpp" +#include "EnvironmentMgr.hpp" #include #include -#include struct modAttribute { modAttribute(const std::string &_name, const std::string &_value) : - name(_name), value(_value) { }; + name(_name), value(_value), duplicateSaveValueOk(false), doNotSet(false) { }; ~modAttribute() = default; std::string name; std::string value; @@ -35,6 +35,7 @@ struct modAttribute { std::string cookedValue; std::string saveValue; bool doNotSet; + bool duplicateSaveValueOk; }; class EnvironmentMgr; @@ -46,30 +47,34 @@ class Operation Operation() : m_count("1"), m_startIndex("0") {} virtual ~Operation() = default; - bool execute(EnvironmentMgr *pEnvMgr, Inputs *pInputs); - void setPath(const std::string &path) { m_path = path; } - std::string getPath() const { return m_path; } - void setParentNodeId(const std::string &id) { m_parentNodeId = id; } - std::string getParentNodeId() const { return m_parentNodeId; } - bool hasNodeId() const { return !m_parentNodeId.empty(); } + bool execute(EnvironmentMgr *pEnvMgr, Variables *pInputs); void addAttribute(modAttribute &newAttribute); - void assignAttributeCookedValues(Inputs *pInputs); - void setCount(const std::string &count) { m_count = count; } - void setStartIndex(const std::string &startIndex) { m_startIndex = startIndex; } + void assignAttributeCookedValues(Variables *pInputs); protected: - virtual void doExecute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) = 0; + virtual void doExecute(EnvironmentMgr *pEnvMgr, Variables *pInputs) = 0; + void getParentNodeIds(EnvironmentMgr *pEnvMgr, Variables *pInputs); + std::shared_ptr createInput(std::string inputName, const std::string &inputType, Variables *pInputs, bool existingOk); + void createAttributeSaveInputs(Variables *pInputs); + void saveAttributeValues(Variables *pInputs, const std::shared_ptr &pEnvNode); protected: std::string m_path; std::string m_parentNodeId; + std::vector m_parentNodeIds; std::vector m_attributes; std::string m_count; std::string m_startIndex; + bool m_throwOnEmpty = true; + std::string m_saveNodeIdName; + bool m_duplicateSaveNodeIdInputOk = false; + + + friend class EnvModTemplate; }; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationCreateNode.cpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationCreateNode.cpp index e121148f736..fbbcb1772e3 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/OperationCreateNode.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/OperationCreateNode.cpp @@ -20,68 +20,41 @@ #include "EnvironmentMgr.hpp" #include "EnvironmentNode.hpp" #include "EnvironmentValue.hpp" -#include "TemplateException.hpp" +#include "TemplateExecutionException.hpp" #include "Status.hpp" -#include - -void OperationCreateNode::doExecute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) +void OperationCreateNode::doExecute(EnvironmentMgr *pEnvMgr, Variables *pInputs) { std::shared_ptr pNewEnvNode; - std::vector parentNodeIds; // // Find the parent node(s). Either was input, or based on a path, which may match more than one node - if (!m_parentNodeId.empty()) - { - parentNodeIds.emplace_back(m_parentNodeId); - } - else - { - std::vector> envNodes; - pEnvMgr->fetchNodes(m_path, envNodes); - for (auto &envNode: envNodes) - parentNodeIds.emplace_back(envNode->getId()); - } + getParentNodeIds(pEnvMgr, pInputs); // // Create an input to hold the newly created node ID(s) if indicated. The IDs are saved as the node(s) is/are // created. - std::shared_ptr pSaveNodeIdInput; + std::shared_ptr pSaveNodeIdInput; if (!m_saveNodeIdName.empty()) { - pSaveNodeIdInput = inputValueFactory("string", m_saveNodeIdName); - pInputs->add(pSaveNodeIdInput); + pSaveNodeIdInput = createInput(m_saveNodeIdName, "string", pInputs, m_duplicateSaveNodeIdInputOk); } // - // If any attribute values are to be saved from the created node(s), create the inputs. - for (auto &attr: m_attributes) - { - // - // If this is a saved attribute value, make sure the input exists - if (!attr.saveValue.empty()) - { - std::shared_ptr pInput = pInputs->getInput(attr.saveValue, false); - if (pInput) - { - throw TemplateException("Attribute '" + attr.name + "' save value '" + attr.saveValue + "' already exists.", false); - } - pInput = inputValueFactory("string", attr.saveValue); - pInputs->add(pInput); - } - } - + // Create any attribute save inputs + createAttributeSaveInputs(pInputs); // // Execute for each parent node - if (!parentNodeIds.empty()) + if (!m_parentNodeIds.empty()) { - for (auto parentNodeId: parentNodeIds) + for (auto &parentNodeId: m_parentNodeIds) { Status status; + std::string nodeId = pInputs->doValueSubstitution(parentNodeId); + // // Get a new node for insertion (this does not insert the node, but rather returns an orphaned node that // can be inserted) @@ -102,15 +75,14 @@ void OperationCreateNode::doExecute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) // // Add the new node to the environment - pNewEnvNode = pEnvMgr->addNewEnvironmentNode(parentNodeId, m_nodeType, attrValues, status); + pNewEnvNode = pEnvMgr->addNewEnvironmentNode(parentNodeId, m_nodeType, attrValues, status, true, true); if (pNewEnvNode) { // construct a status for just this new node's ID so we can see if there is an error Status newNodeStatus(status, pNewEnvNode->getId()); if (newNodeStatus.isError()) { - throw TemplateException("There was a problem adding the new node, status returned an error", - false); + throw TemplateExecutionException("There was a problem adding the new node, status returned an error"); } // @@ -121,29 +93,22 @@ void OperationCreateNode::doExecute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) } // - // Save any required attribute values - for (auto &attr: m_attributes) - { - if (!attr.saveValue.empty()) - { - std::shared_ptr pInput = pInputs->getInput(attr.saveValue); - pInput->addValue(pNewEnvNode->getAttribute(attr.name)->getValue()); - } - } + // Save any attribute values to inputs for use later + saveAttributeValues(pInputs, pNewEnvNode); } else { - throw TemplateException("There was a problem adding the new node", false); + throw TemplateExecutionException("There was a problem adding the new node"); } } else { - throw TemplateException("Unable to get new node", false); + throw TemplateExecutionException("Unable to get new node"); } } } else { - throw TemplateException("Unable to find parent node", false); + throw TemplateExecutionException("Unable to find parent node"); } } diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationCreateNode.hpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationCreateNode.hpp index 83a0392b644..2d0aa91e556 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/OperationCreateNode.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/OperationCreateNode.hpp @@ -20,29 +20,25 @@ #include "Operation.hpp" -#include "../../RapidJSON/include/rapidjson/document.h" - class OperationCreateNode : public Operation { public: OperationCreateNode() = default; - ~OperationCreateNode() = default; - void setNodeType(const std::string &type) { m_nodeType = type; } - std::string getNodeType() const { return m_nodeType; } - void setSaveNodeIdName(const std::string &name) { m_saveNodeIdName = name; } - std::string getSaveNodeIdName() const { return m_saveNodeIdName; } + ~OperationCreateNode() override = default; protected: - void doExecute(EnvironmentMgr *pEnvMgr, Inputs *pInputs); + void doExecute(EnvironmentMgr *pEnvMgr, Variables *pInputs) override; + - private: + protected: std::string m_nodeType; - std::string m_saveNodeIdName; + + friend class EnvModTemplate; }; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationDeleteNode.cpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationDeleteNode.cpp new file mode 100644 index 00000000000..02026c59b65 --- /dev/null +++ b/configuration/configmgr/configmgrlib/mod_template_support/OperationDeleteNode.cpp @@ -0,0 +1,27 @@ + + +#include "OperationDeleteNode.hpp" +#include "EnvironmentMgr.hpp" +#include "TemplateExecutionException.hpp" + +void OperationDeleteNode::doExecute(EnvironmentMgr *pEnvMgr, Variables *pInputs) +{ + // + // Get nodes + getParentNodeIds(pEnvMgr, pInputs); + + // + // If any node IDs found, go delete them + if (!m_parentNodeIds.empty()) + { + for (auto &parentNodeId: m_parentNodeIds) + { + std::string nodeId = pInputs->doValueSubstitution(parentNodeId); + pEnvMgr->removeEnvironmentNode(nodeId); + } + } + else if (m_throwOnEmpty) + { + throw TemplateExecutionException("Unable to find parent node"); + } +} diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationNoop.hpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationDeleteNode.hpp similarity index 67% rename from configuration/configmgr/configmgrlib/mod_template_support/OperationNoop.hpp rename to configuration/configmgr/configmgrlib/mod_template_support/OperationDeleteNode.hpp index d3abe27f65c..6c6eaa06f75 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/OperationNoop.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/OperationDeleteNode.hpp @@ -15,24 +15,25 @@ limitations under the License. ############################################################################## */ -#ifndef HPCCSYSTEMS_PLATFORM_OPERATIONNOOP_HPP -#define HPCCSYSTEMS_PLATFORM_OPERATIONNOOP_HPP +#ifndef HPCCSYSTEMS_PLATFORM_OPERATIONDELETENODE_HPP +#define HPCCSYSTEMS_PLATFORM_OPERATIONDELETENODE_HPP #include "Operation.hpp" -class OperationNoop : public Operation +class OperationDeleteNode :public Operation { public: - OperationNoop() = default; - ~OperationNoop() = default; + OperationDeleteNode() = default; + ~OperationDeleteNode() override = default; protected: - void doExecute(EnvironmentMgr *pEnvMgr, Inputs *pInputs); + void doExecute(EnvironmentMgr *pEnvMgr, Variables *pVariables) override; + friend class EnvModTemplate; }; -#endif //HPCCSYSTEMS_PLATFORM_OPERATIONNOOP_HPP +#endif //HPCCSYSTEMS_PLATFORM_OPERATIONDELETENODE_HPP diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationFindNode.cpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationFindNode.cpp new file mode 100644 index 00000000000..abd6816dda4 --- /dev/null +++ b/configuration/configmgr/configmgrlib/mod_template_support/OperationFindNode.cpp @@ -0,0 +1,93 @@ +/*############################################################################## + + HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®. + + 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 "OperationFindNode.hpp" +#include "EnvironmentMgr.hpp" +#include "EnvironmentNode.hpp" +#include "TemplateExecutionException.hpp" +#include "Status.hpp" +#include "Operation.hpp" + + +void OperationFindNode::doExecute(EnvironmentMgr *pEnvMgr, Variables *pVariables) +{ + // + // Get nodes + getParentNodeIds(pEnvMgr, pVariables); + + // + // Save node ids if found + if (!m_parentNodeIds.empty()) + { + // + // The main reason to use a find nodes action is to either save the IDs of the matching nodes and/or attribute + // values from the matching nodes + std::shared_ptr pSaveNodeIdVar; + if (!m_saveNodeIdName.empty()) + { + pSaveNodeIdVar = createInput(m_saveNodeIdName, "string", pVariables, m_duplicateSaveNodeIdInputOk); + } + createAttributeSaveInputs(pVariables); + + if (pSaveNodeIdVar || !m_attributes.empty()) + { + for (auto &parentNodeId: m_parentNodeIds) + { + std::string nodeId = pVariables->doValueSubstitution(parentNodeId); + if (pSaveNodeIdVar) + { + pSaveNodeIdVar->addValue(nodeId); + } + + // + // Attributes would only be present if values were to be saved + if (!m_attributes.empty()) + { + auto pEnvNode = pEnvMgr->findEnvironmentNodeById(nodeId); + saveAttributeValues(pVariables, pEnvNode); + } + } + } + } + + // + // Otherwise, no nodes found, go create? Can only create if the target type was a path + else if (m_createIfNotFound && !m_path.empty()) + { + // + // To create the node, use the node type. If no node type was specified, use the last part of the path. + if (m_nodeType.empty()) + { + std::size_t lastSlashPos = m_path.find_last_of('/'); + if (lastSlashPos != std::string::npos) + { + m_nodeType = m_path.substr(lastSlashPos + 1); + m_path = m_path.substr(0, lastSlashPos); + } + else + { + throw TemplateExecutionException("Invalid path for find operation, unable to create node"); + } + } + OperationCreateNode::doExecute(pEnvMgr, pVariables); + } + else if (m_throwOnEmpty) + { + throw TemplateExecutionException("Unable to find parent node"); + } +} diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationFindNode.hpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationFindNode.hpp new file mode 100644 index 00000000000..d41cb7040fc --- /dev/null +++ b/configuration/configmgr/configmgrlib/mod_template_support/OperationFindNode.hpp @@ -0,0 +1,45 @@ +/*############################################################################## + + HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®. + + 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. +############################################################################## */ + +#ifndef HPCCSYSTEMS_PLATFORM_OPERATIONFINDNODE_HPP +#define HPCCSYSTEMS_PLATFORM_OPERATIONFINDNODE_HPP + +#include "OperationCreateNode.hpp" + +class OperationFindNode : public OperationCreateNode +{ + public: + + OperationFindNode() = default; + ~OperationFindNode() override = default; + + + protected: + + void doExecute(EnvironmentMgr *pEnvMgr, Variables *pVariables) override; + + + private: + + bool m_createIfNotFound = false; + + + friend class EnvModTemplate; +}; + + +#endif //HPCCSYSTEMS_PLATFORM_OPERATIONFINDNODE_HPP diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationModifyNode.cpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationModifyNode.cpp new file mode 100644 index 00000000000..f838206c3ed --- /dev/null +++ b/configuration/configmgr/configmgrlib/mod_template_support/OperationModifyNode.cpp @@ -0,0 +1,65 @@ +/*############################################################################## + + HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®. + + 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 "OperationModifyNode.hpp" + +#include "Operation.hpp" +#include "TemplateExecutionException.hpp" + + +void OperationModifyNode::doExecute(EnvironmentMgr *pEnvMgr, Variables *pInputs) +{ + std::shared_ptr pEnvNode; + getParentNodeIds(pEnvMgr, pInputs); + + // + // Execute for each parent node + if (!m_parentNodeIds.empty()) + { + for (auto &parentNodeId: m_parentNodeIds) + { + Status status; + + std::string nodeId = pInputs->doValueSubstitution(parentNodeId); + pEnvNode = pEnvMgr->findEnvironmentNodeById(nodeId); + if (pEnvNode) + { + // + // Set the indicated attribute values + std::vector attrValues; + for (auto &attr: m_attributes) + { + if (!attr.doNotSet) + { + attrValues.emplace_back(NameValue(attr.name, attr.cookedValue)); + } + } + pEnvNode->setAttributeValues(attrValues, status, true, true); + } + else + { + // todo base execute method in Opeeration class should catch this exception and rethrow a standard exception + throw TemplateExecutionException("There was an error retrieving a node for modification"); + } + } + } + else if (m_throwOnEmpty) + { + throw TemplateExecutionException("Unable to find parent node"); + } + +} diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationModifyNode.hpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationModifyNode.hpp new file mode 100644 index 00000000000..2f675f62883 --- /dev/null +++ b/configuration/configmgr/configmgrlib/mod_template_support/OperationModifyNode.hpp @@ -0,0 +1,44 @@ +/*############################################################################## + + HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®. + + 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. +############################################################################## */ + +#ifndef HPCCSYSTEMS_PLATFORM_OPERATIONMODIFYNODE_HPP +#define HPCCSYSTEMS_PLATFORM_OPERATIONMODIFYNODE_HPP + +#include "Operation.hpp" + +class OperationModifyNode : public Operation +{ + public: + + OperationModifyNode() = default; + ~OperationModifyNode() override = default; + void addAttributeForDeletion(std::string name) { m_deleteAttributes.emplace_back(name); } + + + protected: + + void doExecute(EnvironmentMgr *pEnvMgr, Variables *pInputs) override; + + + protected: + + std::vector m_deleteAttributes; + +}; + + +#endif //HPCCSYSTEMS_PLATFORM_OPERATIONMODIFYNODE_HPP diff --git a/configuration/configmgr/configmgrlib/mod_template_support/OperationNoop.cpp b/configuration/configmgr/configmgrlib/mod_template_support/OperationNoop.cpp deleted file mode 100644 index fbe43d30104..00000000000 --- a/configuration/configmgr/configmgrlib/mod_template_support/OperationNoop.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/*############################################################################## - - HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®. - - 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 "OperationNoop.hpp" -#include "EnvironmentMgr.hpp" -#include "EnvironmentNode.hpp" - -void OperationNoop::doExecute(EnvironmentMgr *pEnvMgr, Inputs *pInputs) -{ - return; -} diff --git a/configuration/configmgr/configmgrlib/mod_template_support/TemplateException.cpp b/configuration/configmgr/configmgrlib/mod_template_support/TemplateException.cpp index 16c422284d9..12fadc5f827 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/TemplateException.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/TemplateException.cpp @@ -21,5 +21,4 @@ TemplateException::TemplateException(const rapidjson::Document *pDocument) { m_reason = "There was an error parsing the JSON, offset: " + std::to_string(pDocument->GetErrorOffset()) + ", error = " + rapidjson::GetParseError_En(pDocument->GetParseError()); - setBadJson(true); } diff --git a/configuration/configmgr/configmgrlib/mod_template_support/TemplateException.hpp b/configuration/configmgr/configmgrlib/mod_template_support/TemplateException.hpp index 0c678742b6f..0d86bce2c9c 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/TemplateException.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/TemplateException.hpp @@ -27,15 +27,11 @@ class TemplateException : public std::exception { public: - TemplateException(const rapidjson::Document *pDocument); - TemplateException(const std::string &reason, bool badTemplate = true) : m_reason(reason), m_invalidTemplate(badTemplate) { }; - TemplateException() { }; - - void setBadJson(bool isBad) { m_badJson = isBad; } - bool isBadJson() const { return m_badJson; } - void setInvalidTemplate(bool isBad) { m_invalidTemplate = isBad; } - bool isInvalidTemplate() const { return m_invalidTemplate; } + explicit TemplateException(const rapidjson::Document *pDocument); + explicit TemplateException(const std::string &reason, bool badTemplate = false) : m_reason(reason), m_invalidTemplate(badTemplate) { }; + TemplateException() = default; + bool isTemplateInvalid() const { return m_invalidTemplate; } const char *what() const throw() override { return m_reason.c_str(); @@ -45,7 +41,6 @@ class TemplateException : public std::exception private: std::string m_reason; - bool m_badJson = false; bool m_invalidTemplate = false; }; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/TemplateExecutionException.hpp b/configuration/configmgr/configmgrlib/mod_template_support/TemplateExecutionException.hpp new file mode 100644 index 00000000000..0718d3c9872 --- /dev/null +++ b/configuration/configmgr/configmgrlib/mod_template_support/TemplateExecutionException.hpp @@ -0,0 +1,51 @@ +/*############################################################################## + + HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®. + + 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. +############################################################################## */ + +#ifndef HPCCSYSTEMS_PLATFORM_TEMPLATEEXECUTIONEXCEPTION_HPP +#define HPCCSYSTEMS_PLATFORM_TEMPLATEEXECUTIONEXCEPTION_HPP + +#include +#include +#include "rapidjson/document.h" +#include "rapidjson/error/en.h" + +class TemplateExecutionException : public std::exception +{ + public: + + TemplateExecutionException(const std::string &reason) : + m_reason(reason) { } + TemplateExecutionException() = default; + + void setStep(const std::string &step) + { + m_reason = "There was a problem executing " + step + ", the cause is " + m_reason; + } + + const char *what() const throw() override + { + return m_reason.c_str(); + } + + + private: + + std::string m_reason; +}; + + +#endif //HPCCSYSTEMS_PLATFORM_TEMPLATEEXECUTIONEXCEPTION_HPP diff --git a/configuration/configmgr/configmgrlib/mod_template_support/Input.cpp b/configuration/configmgr/configmgrlib/mod_template_support/Variable.cpp similarity index 74% rename from configuration/configmgr/configmgrlib/mod_template_support/Input.cpp rename to configuration/configmgr/configmgrlib/mod_template_support/Variable.cpp index 8c4131a4373..7e55edda35f 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/Input.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/Variable.cpp @@ -15,45 +15,44 @@ limitations under the License. ############################################################################## */ -#include "Input.hpp" +#include "Variable.hpp" #include "TemplateException.hpp" -#include "IPAddressRangeInput.hpp" -#include "IPAddressInput.hpp" -#include "HostNameInput.hpp" +#include "IPAddressRangeVariable.hpp" +#include "IPAddressVariable.hpp" +#include "HostNameVariable.hpp" #include // todo add more types that correspond to XSD types so that modification template inputs can be validated earlier -std::shared_ptr inputValueFactory(const std::string &type, const std::string &name) +std::shared_ptr variableFactory(const std::string &type, const std::string &name) { - std::shared_ptr pInput; + std::shared_ptr pInput; if (type == "string") { - pInput = std::make_shared(); + pInput = std::make_shared(name); } else if (type == "iprange") { - pInput = std::make_shared(); + pInput = std::make_shared(name); } else if (type == "ipaddress") { - pInput = std::make_shared(); + pInput = std::make_shared(name); } else if (type == "hostname") { - pInput = std::make_shared(); + pInput = std::make_shared(name); } else { throw TemplateException("Invalid input type '" + type + "'"); } - pInput->setName(name); return pInput; } -std::string Input::getValue(size_t idx) const +std::string Variable::getValue(size_t idx) const { // // If no value assigned yet, throw an exception @@ -82,14 +81,14 @@ std::string Input::getValue(size_t idx) const } -void Input::setValue(const std::string &value) +void Variable::addValue(const std::string &value) { - if (!m_values.empty()) - { - m_values[0] = value; - } - else - { +// if (!m_values.empty()) +// { +// m_values[0] = value; +// } +// else +// { m_values.emplace_back(value); - } +// } } diff --git a/configuration/configmgr/configmgrlib/mod_template_support/Input.hpp b/configuration/configmgr/configmgrlib/mod_template_support/Variable.hpp similarity index 62% rename from configuration/configmgr/configmgrlib/mod_template_support/Input.hpp rename to configuration/configmgr/configmgrlib/mod_template_support/Variable.hpp index 16dc1ff032e..e811f1c23f0 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/Input.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/Variable.hpp @@ -24,32 +24,26 @@ #include #include -class Input; +class Variable; -std::shared_ptr inputValueFactory(const std::string &type, const std::string &name); +std::shared_ptr variableFactory(const std::string &type, const std::string &name); -class Input +class Variable { public: - Input() = default; - virtual ~Input() = default; + explicit Variable(const std::string &name) : m_name(name) {} + virtual ~Variable() = default; const std::string &getName() const { return m_name; } - void setName(const std::string &name) { m_name = name; } const std::string &getUserPrompt() const { return m_userPrompt; } - void setUserPrompt(const std::string &userPrompt) { m_userPrompt = userPrompt; } const std::string &getDescription() const { return m_description; } - void setDescription(const std::string &description) { m_description = description; } - const std::string &getTooltip() const { return m_tooltip; } - void setTooltip(const std::string &tooltip) { m_tooltip = tooltip; } size_t getNumValues() const { return m_values.size(); } - virtual void setValue(const std::string &value); - void addValue(const std::string &value) { m_values.emplace_back(value); } + virtual void addValue(const std::string &value); +// void addValue(const std::string &value) { m_values.emplace_back(value); } virtual std::string getValue(size_t idx) const; - void setPreparedValue(const std::string &value) { m_preparedValue = value; } + bool isUserInput() const { return m_userInput; } const std::string &getPreparedValue() const { return m_preparedValue; } - void setNonUserInput(bool val) { m_nonUserInput = val; } - bool needsUserInput() const { return !m_nonUserInput; } + protected: @@ -57,10 +51,12 @@ class Input std::string m_name; std::string m_userPrompt; std::string m_description; - std::string m_tooltip; std::string m_preparedValue; - bool m_nonUserInput = false; + bool m_userInput = true; std::vector m_values; + + + friend class EnvModTemplate; }; diff --git a/configuration/configmgr/configmgrlib/mod_template_support/Inputs.cpp b/configuration/configmgr/configmgrlib/mod_template_support/Variables.cpp similarity index 73% rename from configuration/configmgr/configmgrlib/mod_template_support/Inputs.cpp rename to configuration/configmgr/configmgrlib/mod_template_support/Variables.cpp index 5e0e8f3142a..420cfea1292 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/Inputs.cpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/Variables.cpp @@ -15,59 +15,71 @@ limitations under the License. ############################################################################## */ -#include "Input.hpp" -#include "Inputs.hpp" +#include "Variable.hpp" +#include "Variables.hpp" #include "TemplateException.hpp" -void Inputs::add(const std::shared_ptr pNewInput) +void Variables::add(const std::shared_ptr pVariable) { - for (auto &pInput: m_inputValues) + for (auto &pVar: m_variables) { - if (pInput->getName() == pNewInput->getName()) + if (pVar->getName() == pVariable->getName()) { - throw TemplateException("Input '" + pNewInput->getName() + "' is a duplicate."); + throw TemplateException("Variable '" + pVariable->getName() + "' is a duplicate.", true); } } - m_inputValues.emplace_back(pNewInput); + m_variables.emplace_back(pVariable); } -std::shared_ptr Inputs::getInput(const std::string &name, bool throwIfNotFound) const +std::shared_ptr Variables::getVariable(const std::string &name, bool throwIfNotFound) const { - std::shared_ptr pRetInput; - for (auto &pInput: m_inputValues) + std::shared_ptr pRetVar; + std::string varName = name; + + // + // Accept both a regular string or a {{name}} string for the input name + std::size_t bracesStartPos = name.find("{{"); + if (bracesStartPos != std::string::npos) + { + std::size_t bracesEndPos = findClosingDelimiter(name, bracesStartPos,"{{", "}}"); + varName = name.substr(bracesStartPos + 2, bracesEndPos - bracesStartPos - 2); + } + + + for (auto &pVar: m_variables) { - if (pInput->getName() == name) + if (pVar->getName() == varName) { - pRetInput = pInput; + pRetVar = pVar; break; } } - if (!pRetInput && throwIfNotFound) + if (!pRetVar && throwIfNotFound) { - throw TemplateException("Unable to find input, name = '" + name + "'."); + throw TemplateException("Unable to find variable, name = '" + name + "'."); } - return pRetInput; + return pRetVar; } -void Inputs::prepare() +void Variables::prepare() { - for (auto &pInput: m_inputValues) + for (auto &pVar: m_variables) { - std::string preparedValue = pInput->getPreparedValue(); + std::string preparedValue = pVar->getPreparedValue(); if (!preparedValue.empty()) { - pInput->setValue(doValueSubstitution(preparedValue)); + pVar->addValue(doValueSubstitution(preparedValue)); } } } -std::string Inputs::doValueSubstitution(const std::string &value) const +std::string Variables::doValueSubstitution(const std::string &value) const { // // A value has the form {{name}}[{{index}}] where name and index can be simple strings and the index is optional @@ -92,7 +104,7 @@ std::string Inputs::doValueSubstitution(const std::string &value) const if (bracketStartPos != std::string::npos && sizePos != std::string::npos) { - throw TemplateException("Both [] and .size may not appear in a variable input"); + throw TemplateException("Both [] and .size may not appear in a variable variable"); } if (bracketStartPos != std::string::npos) @@ -109,11 +121,11 @@ std::string Inputs::doValueSubstitution(const std::string &value) const if (sizePos != std::string::npos) { - result = std::to_string(getInput(varName, true)->getNumValues()); + result = std::to_string(getVariable(varName, true)->getNumValues()); } else { - std::string substitueValue = doValueSubstitution(getInput(varName, true)->getValue(index)); + std::string substitueValue = doValueSubstitution(getVariable(varName, true)->getValue(index)); std::string newResult = result.substr(0, bracesStartPos); newResult += substitueValue; newResult += result.substr(bracesEndPos + 2); @@ -132,7 +144,7 @@ std::string Inputs::doValueSubstitution(const std::string &value) const -std::size_t Inputs::findClosingDelimiter(const std::string &input, std::size_t startPos, const std::string &openDelim, const std::string &closeDelim) const +std::size_t Variables::findClosingDelimiter(const std::string &input, std::size_t startPos, const std::string &openDelim, const std::string &closeDelim) const { std::size_t curPos = startPos + openDelim.length(); std::size_t openPos, closePos; @@ -174,7 +186,7 @@ std::size_t Inputs::findClosingDelimiter(const std::string &input, std::size_t s } -std::string Inputs::evaluate(const std::string &expr) const +std::string Variables::evaluate(const std::string &expr) const { std::size_t opPos; std::string result = expr; @@ -211,7 +223,7 @@ std::string Inputs::evaluate(const std::string &expr) const } -void Inputs::clear() +void Variables::clear() { - m_inputValues.clear(); + m_variables.clear(); } diff --git a/configuration/configmgr/configmgrlib/mod_template_support/Inputs.hpp b/configuration/configmgr/configmgrlib/mod_template_support/Variables.hpp similarity index 77% rename from configuration/configmgr/configmgrlib/mod_template_support/Inputs.hpp rename to configuration/configmgr/configmgrlib/mod_template_support/Variables.hpp index 7691aa26bd8..731078e3cca 100644 --- a/configuration/configmgr/configmgrlib/mod_template_support/Inputs.hpp +++ b/configuration/configmgr/configmgrlib/mod_template_support/Variables.hpp @@ -23,17 +23,17 @@ #include #include -class Input; +class Variable; -class Inputs +class Variables { public: - Inputs() = default; - ~Inputs() = default; - void add(const std::shared_ptr pInput); - const std::vector> &all(unsigned phase=0) const { return m_inputValues; } - std::shared_ptr getInput(const std::string &name, bool throwIfNotFound = true) const; + Variables() = default; + ~Variables() = default; + void add(const std::shared_ptr pVariable); + const std::vector> &all() const { return m_variables; } + std::shared_ptr getVariable(const std::string &name, bool throwIfNotFound = true) const; void setInputIndex(size_t idx) { m_curIndex = idx; } std::string doValueSubstitution(const std::string &value) const; void prepare(); @@ -48,7 +48,7 @@ class Inputs private: - std::vector> m_inputValues; + std::vector> m_variables; size_t m_curIndex = 0; }; diff --git a/initfiles/componentfiles/configschema/templates/AddKeys.json b/initfiles/componentfiles/configschema/templates/AddKeys.json new file mode 100644 index 00000000000..f297e5e9016 --- /dev/null +++ b/initfiles/componentfiles/configschema/templates/AddKeys.json @@ -0,0 +1,72 @@ +{ + "name" : "Add Key", + "type" : "modification", + "inputs" : [ + { + "name" : "keypair_name", + "type" : "string", + "value" : "mythor" + }, + { + "name" : "public_key", + "type" : "string", + "value" : "/home/jsmith/.ssh/id_rsa.pub.pem" + }, + { + "name" : "private_key", + "type" : "string", + "value" : "/home/jsmith/.ssh/id_rsa" + }, + { + "name" : "key_pair_name", + "type" : "string", + "value" : "mythor" + } + ], + "operations" : [ + { + "action" : "find_node", + "target_type" : "path", + "target" : "/Environment/EnvSettings/Keys", + "node_type" : "keys", + "save_nodeid" : "keys_node", + "create" : true + }, + { + "action" : "create_node", + "target_type" : "nodeid", + "target" : "{{keys_node}}", + "node_type" : "KeyPair", + "attributes" : [ + { + "name" : "name", + "value" : "{{keypair_name}}" + }, + { + "name" : "publicKey", + "value" : "{{public_key}}" + }, + { + "name" : "privakeKey", + "value" : "{{private_key}}" + } + ] + }, + { + "action" : "create_node", + "target_type" : "nodeid", + "target" : "{{keys_node}}", + "node_type" : "Cluster", + "attributes" : [ + { + "name" : "name", + "value" : "{{keypair_name}}" + }, + { + "name" : "keyPairName", + "value" : "{{keypair_name}}" + } + ] + } + ] +} diff --git a/initfiles/componentfiles/configschema/templates/schema/ModTemplateSchema.json b/initfiles/componentfiles/configschema/templates/schema/ModTemplateSchema.json index 1831d783c60..3d2dd241875 100644 --- a/initfiles/componentfiles/configschema/templates/schema/ModTemplateSchema.json +++ b/initfiles/componentfiles/configschema/templates/schema/ModTemplateSchema.json @@ -1,166 +1,349 @@ { - "$schema" : "http://json-schema.org/draft-04/schema#", - "description" : "Schema for validating environment modification template", - "type" : "object", - "properties" : { - "name" : { - "type" : "string" + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Schema for validating environment modification template", + "type": "object", + "properties": { + "name": { + "type": "string", + "description" : "Name for the template" }, - "description" : { - "type" : "string" + "description": { + "type": "string", + "description" : "A description of the template" }, - "type" : { - "type" : "string", - "enum" : [ + "type": { + "type": "string", + "enum": [ "new", "modification" - ] - }, - "subtype" : { - "type" : "string" + ], + "description" : "Type of template. Mainly used for filtering templates." }, - "inputs" : { - "type" : "array", - "minItems" : 0, - "items" : { - "type" : "object", - "properties" : { - "name" : { - "type" : "string" + "variables": { + "type": "array", + "minItems": 0, + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description" : "Name of the variable to which the input is assigned. This value is available using {{name}} anywhere in the template" }, - "type" : { - "type" : "string", - "enum" : [ + "type": { + "type": "string", + "enum": [ "string", "iprange", "ipaddress", "hostname" - ] - }, - "prompt" : { - "type" : "string" + ], + "description" : "The variable type." }, - "description" : { - "type" : "string" + "prompt": { + "type": "string", + "description" : "String shown to user during interactive input" }, - "tooltip" : { - "type" : "string" + "description": { + "type": "string", + "description" : "Description of what this value represents in the template." }, - "value" : { - "type" : "string" + "values": { + "type": "array", + "items" : { + "type" : "string" + }, + "description" : "Pre-assigned values" }, - "non_user_input" : { - "type" : "boolean" + "user_input": { + "type": "boolean", + "description" : "Set to true if user must input this value(s) (defaults to true)", + "default" : true }, - "prepared_value" : { - "type" : "string" + "prepared_value": { + "type": "string", + "description" : "Optional. Used to prepare the final value for use during template execution. Usually combined with other variable values of the form {{varname}}" } }, - "required" : [ + "required": [ "name", "type" ] - } - }, - "rules" : { - "type" : "object" + }, + "description" : "Array of variables for template execution. Some may require user input." }, - "operations" : { - "type" : "array", - "minItems" : 0, - "items" : { "$ref" : "#/definitions/operation"} + "operations": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/definitions/operation" + } } }, - "required" : [ + "required": [ "name", "type", "operations" ], - - "definitions" : { - - "operation" : { - "type" : "object", - "properties" : { - "action" : { - "type" : "string" - }, - "save_nodeid" : { - "type" : "string" + "definitions": { + "operation": { + "type": "object", + "properties": { + "description": { + "type": "string" }, - "path" : { - "type" : "string" + "action": { + "type" : "string", + "enum": [ + "create_node", + "modify_node", + "find_node", + "delete_node" + ], + "description" : "Action to perform for this operation" }, - "parent_nodeid" : { - "type" : "string" + "parent_path": { + "type": "string" }, - "node_type" : { - "type" : "string" - }, - "count" : { - "type" : "string" - }, - "start_index" : { - "type" : "string" - }, - "attributes" : { - "$ref" : "#/definitions/attribute_values" + "parent_nodeid": { + "type": "string" } }, - "allOf" : [ + "allOf": [ { - "oneOf" : [ - + "oneOf": [ + { + "properties": { + "action": { + "enum": [ + "create_node" + ] + }, + "data": { + "$ref": "#/definitions/create_data_info" + } + }, + "required": [ + "action", + "data" + ] + }, + { + "properties": { + "action": { + "enum": [ + "modify_node" + ] + }, + "data": { + "$ref": "#/definitions/modify_data_info" + } + }, + "required": [ + "action", + "data" + ] + }, { - "properties" : { - "action" : { "enum" : [ "create_node"]} + "properties": { + "action": { + "enum": [ + "find_node" + ] + }, + "data": { + "$ref": "#/definitions/find_data_info" + } }, - "required" : ["node_type"] + "required": [ + "action" + ] }, { - "properties" : { - "action" : { "enum" : ["modify_node", "noop"]} - } + "properties": { + "action": { + "enum": [ + "delete_node" + ] + }, + "data": { + "$ref": "#/definitions/delete_data_info" + } + }, + "required": [ + "action" + ] } ] }, { - "oneOf" : [ - { "required" : [ "path" ]}, - { "required" : [ "parent_nodeid" ]} + "oneOf": [ + { + "required": [ + "parent_path" + ] + }, + { + "required": [ + "parent_nodeid" + ] + } ] + } + ], + "description" : "Definition of the operation to perform" + }, + "create_data_info": { + "type": "object", + "properties": { + "count": { + "type": "string" + }, + "start_index": { + "type": "string" + }, + "node_type": { + "type": "string" }, + "save_nodeid": { + "$ref": "#/definitions/save_info" + }, + "attributes": { + "$ref": "#/definitions/attribute_values" + } + }, + "allOf": [ { - "required" : ["action"] + "required": [ + "node_type" + ] } ] - }, - - "attribute_value" : { - "type" : "object", - "properties" : { - "name" : { - "type" : "string" + "modify_data_info": { + "type": "object", + "properties": { + "count": { + "type": "string" }, - "value" : { - "type" : "string" + "start_index": { + "type": "string" }, - "save_value" : { - "type" : "string" + "save_nodeid": { + "$ref": "#/definitions/save_info" }, - "do_not_set" : { - "type" : "boolean" + "attributes": { + "$ref": "#/definitions/attribute_values" } }, - "required" : ["name", "value"] + "allOf": [ + { + "required": [ + "attributes" + ] + } + ] }, - - "attribute_values" : { - "type" : "array", - "items" : { - "$ref" : "#/definitions/attribute_value" + "find_data_info": { + "type": "object", + "properties": { + "node_type": { + "type": "string" + }, + "save_nodeid": { + "$ref": "#/definitions/save_info" + }, + "error_if_not_found": { + "type": "boolean", + "default" : true + }, + "create": { + "type": "boolean", + "default" : false + }, + "attributes": { + "$ref": "#/definitions/attribute_refs" + } + } + }, + "delete_data_info": { + "type": "object", + "properties": { + "count": { + "type": "string" + }, + "start_index": { + "type": "string" + }, + "error_if_not_found": { + "type": "boolean", + "default" : true + } + } + }, + "attribute_value": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "save_value": { + "$ref": "#/definitions/save_info" + }, + "do_not_set": { + "type": "boolean" + } + }, + "required": [ + "name", + "value" + ] + }, + "attribute_values": { + "type": "array", + "items": { + "$ref": "#/definitions/attribute_value" } + }, + "attribute_ref" : { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "save_value": { + "$ref": "#/definitions/save_info" + } + }, + "allOf": [ + { + "required": [ + "name" + ] + } + ] + }, + "attribute_refs": { + "type": "array", + "items": { + "$ref": "#/definitions/attribute_ref" + } + }, + "save_info": { + "type": "object", + "properties": { + "save_name": { + "type": "string" + }, + "duplicate_ok": { + "type": "boolean" + } + }, + "required": [ + "save_name" + ] } } } diff --git a/testing/unittests/configmgr/ConfigMgrTemplateTests.cpp b/testing/unittests/configmgr/ConfigMgrTemplateTests.cpp index 99902f8e121..0b8e26899bb 100644 --- a/testing/unittests/configmgr/ConfigMgrTemplateTests.cpp +++ b/testing/unittests/configmgr/ConfigMgrTemplateTests.cpp @@ -28,6 +28,7 @@ // Config2 manager includes #include "jexcept.hpp" #include "mod_template_support/TemplateException.hpp" +#include "mod_template_support/TemplateExecutionException.hpp" #include "EnvironmentMgr.hpp" #include "build-config.h" #include @@ -35,7 +36,7 @@ #include #include #include "mod_template_support/EnvModTemplate.hpp" -#include "mod_template_support/IPAddressRangeInput.hpp" +#include "mod_template_support/IPAddressRangeVariable.hpp" // // This class validates that the system XSDs are wellformed and parse with no errors @@ -48,6 +49,8 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture CPPUNIT_TEST(Test_Inputs); CPPUNIT_TEST(Test_SubstitutionTests); CPPUNIT_TEST(Test_OperationsTests); + CPPUNIT_TEST(Test_FindNodeTests); + CPPUNIT_TEST(Test_CreateNodeTests); CPPUNIT_TEST_SUITE_END(); @@ -67,13 +70,22 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture bool loadEnvironment(const std::string &envFilename) { + bool rc = true; + CPPUNIT_ASSERT_MESSAGE("No envionrment manager loaded", m_pEnvMgr != nullptr); std::string xmlEnvDir = INSTALL_DIR PATHSEPSTR "testing/configmgr/schema/environments/"; m_pEnvMgr->discardEnvironment(); // in case one is currently loaded - return(m_pEnvMgr->loadEnvironment(xmlEnvDir + envFilename)); + + if (!m_pEnvMgr->loadEnvironment(xmlEnvDir + envFilename)) + { + std::string msg = "There was a problem loading environment " + envFilename + "Error: " + m_pEnvMgr->getLastEnvironmentMessage(); + CPPUNIT_ASSERT_MESSAGE(msg, false); + rc = false; + } + return rc; } - void CreateEnvironmentManager(const std::string &schemaFilename, const std::string &environmentFile) + void CreateEnvironmentManager(const std::string &schemaFilename) { // // Standard configuration for HPCC @@ -93,20 +105,13 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture std::map cfgParms; rc = m_pEnvMgr->loadSchema(schemaXSDDir, schemaFilename, cfgParms); CPPUNIT_ASSERT_MESSAGE("Unable to load configuration schema, error = " + m_pEnvMgr->getLastSchemaMessage(), rc); - - // - // Load the environment if specified - if (!environmentFile.empty()) - { - bool rc = loadEnvironment(environmentFile); - CPPUNIT_ASSERT_MESSAGE("Unable to load environment, error = " + m_pEnvMgr->getLastEnvironmentMessage(), rc); - } printf("complete."); } void LoadTemplate(const std::string &templateFilename) { + CPPUNIT_ASSERT_MESSAGE("No envionrment manager loaded", m_pEnvMgr != nullptr); printf("\n Instantiating template %s ...", templateFilename.c_str()); std::string templateSchema = INSTALL_DIR PATHSEPSTR "componentfiles/configschema/templates/schema/ModTemplateSchema.json"; if (m_pTemplate != nullptr) @@ -145,7 +150,7 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture } catch (TemplateException &e) { - if (e.isBadJson()) // || e.isInvalidTemplate()) + if (!e.isTemplateInvalid()) { rc = false; assertMsg += e.what(); @@ -164,17 +169,36 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture } + void executeTemplate(bool expectFailure) + { + printf("\n Executing template...");try + { + m_pTemplate->execute(); + } + catch (TemplateExecutionException &e) + { + if (!expectFailure) + { + std::string assertMsg; + assertMsg = "Failed when expected to pass, msg = "; + assertMsg += e.what(); + CPPUNIT_ASSERT_MESSAGE(assertMsg, false); + } + } + printf("complete."); + } + void Test_LoadingTemplates() { bool rc = false; printf("\n*** ConfigMgr 2.0 - Templates - Loading Only ***"); - CreateEnvironmentManager("Simple.xsd", ""); + CreateEnvironmentManager("Simple.xsd"); // // Simple template create printf("\n Instantiating a template..."); std::string templateSchema = INSTALL_DIR PATHSEPSTR "componentfiles/configschema/templates/schema/ModTemplateSchema.json"; - EnvModTemplate *pTemplate = new EnvModTemplate(m_pEnvMgr, templateSchema); + auto *pTemplate = new EnvModTemplate(m_pEnvMgr, templateSchema); printf("complete."); // @@ -184,7 +208,7 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture pTemplate->loadTemplateFromJson("{ \"key\" : \"value\" }"); rc = false; // the load should throw } catch (TemplateException &te) { - rc = te.isInvalidTemplate(); + rc = te.isTemplateInvalid(); } CPPUNIT_ASSERT_MESSAGE("Unable to load JSON from a string.", rc); printf("passed."); @@ -193,10 +217,10 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture // Load invalid JSON from a string and make sure it detects invalid JSON printf("\n Testing invalid JSON parse from a string..."); try { - pTemplate->loadTemplateFromJson("{ \"key : \"value\" }"); + pTemplate->loadTemplateFromJson("{ key\" : \"value\" }"); rc = false; } catch (TemplateException &te) { - rc = te.isBadJson(); + rc = true; } CPPUNIT_ASSERT_MESSAGE("Unable to detect invalid JSON from a string.", rc); printf("passed."); @@ -208,7 +232,7 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture pTemplate->loadTemplateFromFile(m_templateDir + "ParseFromFileTest.json"); rc = true; } catch (TemplateException &te) { - rc = te.isInvalidTemplate(); // this is the expected error + rc = te.isTemplateInvalid(); // this is the expected error } CPPUNIT_ASSERT_MESSAGE("Unable to parse valid JSON.", rc); printf("passed."); @@ -224,25 +248,25 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture std::string msg; bool rc = false; printf("\n*** ConfigMgr 2.0 - Templates - Inputs ***"); - CreateEnvironmentManager("Simple.xsd", ""); + CreateEnvironmentManager("Simple.xsd"); LoadTemplate(""); std::string json; std::vector inputTests; // Inputs testing: validate required values - inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"inputs\" : 7 }", true, "Invalid Inputs section type (non array)")); - inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"inputs\" : [] }", false, "Valid Inputs section type (array)")); - inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"inputs\" : [\"bad\"] }", true, "Invalid Inputs array element (non-object)")); - inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"inputs\" : [{}] }", true, "Invalid Inputs array element (missing required values)")); - inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"inputs\" : [{\"name\":\"x\"}] }", true, "Invalid Inputs array element (missing required values)")); + inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"variables\" : 7 }", true, "Invalid Inputs section type (non array)")); + inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"variables\" : [] }", false, "Valid Inputs section type (array)")); + inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"variables\" : [\"bad\"] }", true, "Invalid Inputs array element (non-object)")); + inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"variables\" : [{}] }", true, "Invalid Inputs array element (missing required values)")); + inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"variables\" : [{\"name\":\"x\"}] }", true, "Invalid Inputs array element (missing required values)")); // Inputs: test types - inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"inputs\" : [{\"name\":\"x\",\"type\":\"string\"}] }", false, "Valid Inputs array element (type 'string')")); - inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"inputs\" : [{\"name\":\"x\",\"type\":\"badtype\"}] }", true, "Invalid Input type")); + inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"variables\" : [{\"name\":\"x\",\"type\":\"string\"}] }", false, "Valid Inputs array element (type 'string')")); + inputTests.emplace_back(templateInfo("{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"variables\" : [{\"name\":\"x\",\"type\":\"badtype\"}] }", true, "Invalid Variable type")); // Inputs: duplicate name - json = "{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"inputs\" : [{\"name\":\"x\",\"type\":\"string\"}, {\"name\":\"x\",\"type\":\"string\"}] }"; + json = "{ \"name\" : \"x\", \"type\" : \"new\", \"operations\" : [], \"variables\" : [{\"name\":\"x\",\"type\":\"string\"}, {\"name\":\"x\",\"type\":\"string\"}] }"; inputTests.emplace_back(templateInfo(json, true, "Invalid, Duplicate input name")); // @@ -253,34 +277,34 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture } // Inputs; retrieving by name with >1 inputs - json = R"({"name" : "x", "type" : "new", "operations" : [], "inputs" : [)"; - json += R"({"name":"x","type":"string", "prompt":"prompt","description":"description","tooltip":"tooltip"})"; + json = R"({"name" : "x", "type" : "new", "operations" : [], "variables" : [)"; + json += R"({"name":"x","type":"string", "prompt":"prompt","description":"description"})"; json += R"(,{"name":"y","type":"string"})"; json += R"(,{"name":"ips","type":"iprange"})"; - json += R"(,{"name":"preset","type":"string","value":"myvalue"})"; + json += R"(,{"name":"preset","type":"string","values": ["myvalue"]})"; json += "] }"; templateInfo ti1(json, false, "Valid multi-inputs"); doTemplateLoad(ti1); printf("\n Test getting inputs from template..."); - std::vector> inputs = m_pTemplate->getInputs(); + std::vector> inputs = m_pTemplate->getVariables(); CPPUNIT_ASSERT_MESSAGE("Number of inputs was not 4", inputs.size() == 4); printf("passed."); printf("\n Test retrieving first defined input 'x'..."); - auto pInput = m_pTemplate->getInput("x", false); + auto pInput = m_pTemplate->getVariable("x", false); CPPUNIT_ASSERT_MESSAGE("Unable to find input named 'x'", pInput); printf("passed."); printf("\n Test retrieving second defined input 'y'..."); - pInput = m_pTemplate->getInput("y", false); + pInput = m_pTemplate->getVariable("y", false); CPPUNIT_ASSERT_MESSAGE("Unable to find input named 'y'", pInput); printf("passed."); // // Test string type inputs (and other input attributes) printf("\n Test retrieving string input 'x'..."); - pInput = m_pTemplate->getInput("x", false); + pInput = m_pTemplate->getVariable("x", false); CPPUNIT_ASSERT_MESSAGE("Unable to find input named 'x'", pInput); printf("passed."); @@ -296,23 +320,18 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture CPPUNIT_ASSERT_MESSAGE(msg, returnedDesc == "description"); printf("passed."); - printf("\n Test retrieving 'tooltip'..."); - std::string returnedTooltip = pInput->getTooltip(); - msg = "Expected tooltip value 'tooltip' did not match returned value '" + returnedTooltip + "'"; - CPPUNIT_ASSERT_MESSAGE(msg, returnedTooltip == "tooltip"); - printf("passed."); // // test string type inputs printf("\n Test retrieving string input 'y'..."); - pInput = m_pTemplate->getInput("y", false); + pInput = m_pTemplate->getVariable("y", false); CPPUNIT_ASSERT_MESSAGE("Unable to find input named 'y'", pInput); printf("passed."); printf("\n Test setting and getting string value..."); if (pInput) { - pInput->setValue("aaaBBBccc"); + pInput->addValue("aaaBBBccc"); CPPUNIT_ASSERT_MESSAGE("Get value did not match the set value (aaaBBBccc)", pInput->getValue(0) == "aaaBBBccc"); } else @@ -325,8 +344,8 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture // IPrange inputs printf("\n Testing IPRange input..."); printf("\n Testing getting an IPRange input pointer..."); - pInput = m_pTemplate->getInput("ips"); - std::shared_ptr pIpRangeInput = std::dynamic_pointer_cast(pInput); + pInput = m_pTemplate->getVariable("ips"); + std::shared_ptr pIpRangeInput = std::dynamic_pointer_cast(pInput); if (!pIpRangeInput) { CPPUNIT_ASSERT_MESSAGE("Unable to dynamic cast input poniter to expected type of IPRange", false); @@ -336,7 +355,7 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture printf("\n Set single IP address..."); try { - pIpRangeInput->setValue("1.2.3.4"); + pIpRangeInput->addValue("1.2.3.4"); rc = pIpRangeInput->getValue(0) == "1.2.3.4"; CPPUNIT_ASSERT_MESSAGE("Return value did not match expected value", rc); } @@ -348,7 +367,7 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture printf("\n Set multiple IP addresses (';' separated IP addresses)..."); try { - pIpRangeInput->setValue("1.2.3.4;1.2.3.5"); + pIpRangeInput->addValue("1.2.3.4;1.2.3.5"); rc = pIpRangeInput->getValue(0) == "1.2.3.4"; } catch (TemplateException &te) @@ -360,7 +379,7 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture printf("\n Set a single range (1.2.3.1-5)..."); try { - pIpRangeInput->setValue("1.2.3.1-5"); + pIpRangeInput->addValue("1.2.3.1-5"); CPPUNIT_ASSERT_MESSAGE("Expected 5 values for variable", pIpRangeInput->getNumValues() == 5); rc = pIpRangeInput->getValue(0) == "1.2.3.1"; @@ -379,7 +398,7 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture printf("\n Set a valid and arange (3.4.5.6;1.2.3.1-5)..."); try { - pIpRangeInput->setValue("3.4.5.6;1.2.3.1-5"); + pIpRangeInput->addValue("3.4.5.6;1.2.3.1-5"); CPPUNIT_ASSERT_MESSAGE("Expected 6 values for variable", pIpRangeInput->getNumValues() == 6); rc = pIpRangeInput->getValue(0) == "3.4.5.6"; @@ -397,11 +416,20 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture printf("complete"); printf("\n Preset value..."); - pInput = m_pTemplate->getInput("preset", false); + pInput = m_pTemplate->getVariable("preset", false); CPPUNIT_ASSERT_MESSAGE("Unable to find input named 'preset'", pInput); CPPUNIT_ASSERT_MESSAGE("Variable does not have expected value", pInput->getValue(0) == "myvalue"); printf("complete"); + // + // Inputs from a file + // + // - need an InputsTest1.json that defines a number of inputs, no operations needed + // - need an InputsForInputsTest1-1.json file that has the inputs + // all defined properly + // - InputsForInputsTest1-2.json has an incorrect input name expect to fail + + // // Test complete delete m_pTemplate; @@ -414,44 +442,106 @@ class ConfigMgrTemplateTests : public CppUnit::TestFixture std::string msg; bool rc = false; printf("\n*** ConfigMgr 2.0 - Templates - Variable substitution tests ***"); - CreateEnvironmentManager("Simple.xsd", "template_test.xml"); - LoadTemplate(""); - - templateInfo si("SubstitutionTest-1.json", false, "Loading variable substitution test 1 template"); - doTemplateLoad(si); - printf("complete."); - - printf("\n Executing template..."); - m_pTemplate->execute(); - printf("complete."); + CreateEnvironmentManager("Simple.xsd"); + LoadTemplate(""); // Just loads the schema + if (loadEnvironment("template_test.xml")) + { + templateInfo si("SubstitutionTest-1.json", false, "Loading variable substitution test 1 template"); + doTemplateLoad(si); + printf("complete."); + printf("\n Executing template..."); + m_pTemplate->execute(); + printf("complete."); + } delete m_pTemplate; } + + void Test_OperationsTests() { std::string msg; bool rc = false; printf("\n*** ConfigMgr 2.0 - Templates - Execute Templates ***"); - CreateEnvironmentManager("Simple.xsd", "template_test.xml"); + CreateEnvironmentManager("Simple.xsd"); // // Test a simple add of a sub element with two attributes // load the template - // get inuts (both string) and set values (should be two) + // get inputs (both string) and set values (should be two) // execute the template // retrieve node from changed environment to validate added properly LoadTemplate("OperationsTest1.json"); + loadEnvironment("findnodes_test.xml"); + executeTemplate(false); - m_pTemplate->execute(); + // + // Test a duplicate save node id input name where it is OK for a duplicate save name + LoadTemplate("OperationsTest2.json"); + loadEnvironment("findnodes_test.xml"); + executeTemplate(false); + // + // Test a duplicate save node id input name where it is not OK for a duplicate save name + LoadTemplate("OperationsTest3.json"); + loadEnvironment("findnodes_test.xml"); + executeTemplate(true); // expect this to fail + // + // Test a duplicate save of attribute values to the same input var name + LoadTemplate("OperationsTest4.json"); + loadEnvironment("findnodes_test.xml"); + executeTemplate(false); + + // + // Test a duplicate save of attribute values to the same input var name where not allowed + LoadTemplate("OperationsTest5.json"); + loadEnvironment("findnodes_test.xml"); + executeTemplate(true); delete m_pTemplate; } + // + // Tests the action find_node + void Test_FindNodeTests() + { + std::string msg; + bool rc = false; + printf("\n*** ConfigMgr 2.0 - Templates - Find Node Tests ***"); + CreateEnvironmentManager("Simple.xsd"); + loadEnvironment("findnodes_test.xml"); + + // + // Execute the template, then query the environment to validate. Note that there + // may some modifications done in order to be able to verify + LoadTemplate("FindNodeTest1.json"); + executeTemplate(false); + } + + + // + // Tests the action find_node + void Test_CreateNodeTests() + { + std::string msg; + bool rc = false; + printf("\n*** ConfigMgr 2.0 - Templates - Create Node Tests ***"); + CreateEnvironmentManager("Simple.xsd"); + loadEnvironment("createnode_test1.xml"); + + // + // Execute the template, then query the environment to validate. Note that there + // may some modifications done in order to be able to verify + LoadTemplate("CreateNodeTest1.json"); + executeTemplate(false); + } + + + private: EnvironmentMgr *m_pEnvMgr; diff --git a/testing/unittests/configmgr/schema/environments/CMakeLists.txt b/testing/unittests/configmgr/schema/environments/CMakeLists.txt index 68654cf32b3..8ac8db1f6a7 100644 --- a/testing/unittests/configmgr/schema/environments/CMakeLists.txt +++ b/testing/unittests/configmgr/schema/environments/CMakeLists.txt @@ -15,12 +15,8 @@ # limitations under the License. ################################################################################ -if (USE_CPPUNIT) - if (USE_CPPUNIT) - FOREACH( iFILES - ${CMAKE_CURRENT_SOURCE_DIR}/template_test.xml - ) - Install ( FILES ${iFILES} DESTINATION testing/configmgr/schema/environments COMPONENT Runtime) - ENDFOREACH ( iFILES ) - endif() -endif() + +FILE(GLOB envFiles "${CMAKE_CURRENT_SOURCE_DIR}/*.xml") +FOREACH( envFile ${envFiles} ) + Install ( FILES ${envFile} DESTINATION testing/configmgr/schema/environments COMPONENT Runtime) +ENDFOREACH () diff --git a/testing/unittests/configmgr/schema/environments/createnode_test1.xml b/testing/unittests/configmgr/schema/environments/createnode_test1.xml new file mode 100644 index 00000000000..49d7938ff0a --- /dev/null +++ b/testing/unittests/configmgr/schema/environments/createnode_test1.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/testing/unittests/configmgr/schema/environments/findnodes_test.xml b/testing/unittests/configmgr/schema/environments/findnodes_test.xml new file mode 100644 index 00000000000..ced83b2cdd5 --- /dev/null +++ b/testing/unittests/configmgr/schema/environments/findnodes_test.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/testing/unittests/configmgr/schema/environments/template_test.xml b/testing/unittests/configmgr/schema/environments/template_test.xml index fd0615102e1..c6e6afaa6ee 100644 --- a/testing/unittests/configmgr/schema/environments/template_test.xml +++ b/testing/unittests/configmgr/schema/environments/template_test.xml @@ -2,4 +2,6 @@ + + diff --git a/testing/unittests/configmgr/schema/xsd/Simple.xsd b/testing/unittests/configmgr/schema/xsd/Simple.xsd index 1cba4721592..f8f769312ac 100644 --- a/testing/unittests/configmgr/schema/xsd/Simple.xsd +++ b/testing/unittests/configmgr/schema/xsd/Simple.xsd @@ -24,10 +24,10 @@ - + - + @@ -36,9 +36,19 @@ - - - + + + + + + + + + + + + + diff --git a/testing/unittests/configmgr/templates/CreateNodeTest1.json b/testing/unittests/configmgr/templates/CreateNodeTest1.json new file mode 100644 index 00000000000..a5935f532a9 --- /dev/null +++ b/testing/unittests/configmgr/templates/CreateNodeTest1.json @@ -0,0 +1,60 @@ +{ + "name" : "", + "type" : "modification", + "inputs" : [ + ], + "operations" : [ + { + "description" : "Create another subchild in the path specified", + "action" : "create_node", + "parent_path" : "/Root/Child1", + "data" : { + "node_type" : "subchild1", + "save_nodeid" : { + "save_name" : "subchild1" + }, + "attributes" : [ + { + "name" : "attr1", + "value" : "value1" + }, + { + "name" : "attr2", + "value" : "4" + } + ] + } + }, + { + "description" : "now modify the attributes in what was added above", + "action" : "modify_node", + "parent_nodeid" : "{{subchild1}}", + "data" : { + "attributes" : [ + { + "name" : "attr1", + "value" : "new_value" + }, + { + "name" : "attr2", + "value" : "5" + } + ] + } + }, + { + "description" : "create a new non defined node (no schema)", + "action" : "find_node", + "parent_path" : "/Root/AdHocNode", + "data" : { + "create" : true, + "attributes" : [ + { + "name" : "newattr1", + "value" : "iamnew" + } + ] + } + } + ] +} diff --git a/testing/unittests/configmgr/templates/FindNodeTest1.json b/testing/unittests/configmgr/templates/FindNodeTest1.json new file mode 100644 index 00000000000..b9bc16ee1de --- /dev/null +++ b/testing/unittests/configmgr/templates/FindNodeTest1.json @@ -0,0 +1,58 @@ +{ + "name" : "Find nodes test 1", + "type" : "modification", + "description" : "Finds a node that exists", + "variables" : [ + { + "name" : "child", + "values" : ["SubChild1"], + "type" : "string" + }, + { + "name" : "ipaddresses", + "type" : "ipaddress" + } + ], + "operations" : [ + { + "action" : "find_node", + "parent_path" : "/Root/Child1/{{child}}", + "data" : { + "save_nodeid" : { + "save_name" : "parent1" + } + } + }, + { + "action" : "find_node", + "parent_path" : "/Root/Child1/SubChild1[@attr1='child1_subchild1_2']" + }, + { + "action" : "find_node", + "parent_path" : "/Root/Child2" + }, + { + "action" : "find_node", + "parent_path" : "/Root/Child3", + "data" : { + "error_if_not_found" : false + } + }, + { + "action" : "modify_node", + "parent_nodeid" : "{{parent1}}", + "data" : { + "attributes" : [ + { + "name" : "attr2", + "value" : "4" + } + ] + } + }, + { + "action" : "find_node", + "parent_path" : "/Root/Child1/SubChild1[@attr2='4']" + } + ] +} diff --git a/testing/unittests/configmgr/templates/OperationsTest1.json b/testing/unittests/configmgr/templates/OperationsTest1.json index b70b6eb36a4..1e3eeb2bd92 100644 --- a/testing/unittests/configmgr/templates/OperationsTest1.json +++ b/testing/unittests/configmgr/templates/OperationsTest1.json @@ -2,33 +2,35 @@ "name" : "test", "type" : "modification", "description" : "add a subchild to an existing node in an environment", - "inputs" : [ + "variables" : [ { "name" : "attr1", "type" : "string", - "value" : "attr1Value" + "values" : ["attr1Value"] }, { "name" : "attr2", "type" : "string", - "value" : "444" + "values" : ["444"] } ], "operations" : [ { "action" : "create_node", - "path" : "/Root/Child1", - "node_type" : "subchild1", - "attributes" : [ - { - "name" : "attr1", - "value" : "{{attr1}}" - }, - { - "name" : "attr2", - "value" : "{{attr2}}" - } - ] + "parent_path" : "/Root/Child1", + "data" : { + "node_type" : "subchild1", + "attributes" : [ + { + "name" : "attr1", + "value" : "{{attr1}}" + }, + { + "name" : "attr2", + "value" : "{{attr2}}" + } + ] + } } ] } diff --git a/testing/unittests/configmgr/templates/OperationsTest2.json b/testing/unittests/configmgr/templates/OperationsTest2.json new file mode 100644 index 00000000000..0758260d11a --- /dev/null +++ b/testing/unittests/configmgr/templates/OperationsTest2.json @@ -0,0 +1,28 @@ +{ + "name" : "", + "type" : "modification", + "description" : "Test allowing duplicate save node ID names, should execute w/o error", + "variables" : [ + ], + "operations" : [ + { + "action" : "find_node", + "parent_path" : "/Root/Child1", + "data" : { + "save_nodeid" : { + "save_name" : "nodes" + } + } + }, + { + "action" : "find_node", + "parent_path" : "/Root/Child2", + "data" : { + "save_nodeid" : { + "save_name" : "nodes", + "duplicate_ok" : true + } + } + } + ] +} diff --git a/testing/unittests/configmgr/templates/OperationsTest3.json b/testing/unittests/configmgr/templates/OperationsTest3.json new file mode 100644 index 00000000000..06afa4b9776 --- /dev/null +++ b/testing/unittests/configmgr/templates/OperationsTest3.json @@ -0,0 +1,28 @@ +{ + "name" : "", + "type" : "modification", + "description" : "Test not allowing duplicate save node ID names, should execture w/o error", + "variables" : [ + ], + "operations" : [ + { + "action" : "find_node", + "parent_path" : "/Root/Child1", + "data" : { + "save_nodeid" : { + "save_name" : "nodes" + } + } + }, + { + "action" : "find_node", + "parent_path" : "/Root/Child2", + "data" : { + "save_nodeid" : { + "save_name" : "nodes", + "duplicate_ok" : false + } + } + } + ] +} diff --git a/testing/unittests/configmgr/templates/OperationsTest4.json b/testing/unittests/configmgr/templates/OperationsTest4.json new file mode 100644 index 00000000000..24ee4823619 --- /dev/null +++ b/testing/unittests/configmgr/templates/OperationsTest4.json @@ -0,0 +1,38 @@ +{ + "name" : "", + "type" : "modification", + "description" : "Test allowing duplicate save of attribute values, should execute w/o error", + "variables" : [ + ], + "operations" : [ + { + "action" : "find_node", + "parent_path" : "/Root/Child1/SubChild1", + "data" : { + "attributes" : [ + { + "name" : "attr1", + "save_value" : { + "save_name" : "attrvalue" + } + } + ] + } + }, + { + "action" : "find_node", + "parent_path" : "/Root/Child2/SubChild2", + "data" : { + "attributes" : [ + { + "name" : "attr1", + "save_value" : { + "save_name" : "attrvalue", + "duplicate_ok" : true + } + } + ] + } + } + ] +} diff --git a/testing/unittests/configmgr/templates/OperationsTest5.json b/testing/unittests/configmgr/templates/OperationsTest5.json new file mode 100644 index 00000000000..fdbb8f0a613 --- /dev/null +++ b/testing/unittests/configmgr/templates/OperationsTest5.json @@ -0,0 +1,38 @@ +{ + "name" : "", + "type" : "modification", + "description" : "Test not allowing duplicate save of attribute values, should cause an error", + "variables" : [ + ], + "operations" : [ + { + "action" : "find_node", + "parent_path" : "/Root/Child1/SubChild1", + "data" : { + "attributes" : [ + { + "name" : "attr1", + "save_value" : { + "save_name" : "attrvalue" + } + } + ] + } + }, + { + "action" : "find_node", + "parent_path" : "/Root/Child2/SubChild2", + "data" : { + "attributes" : [ + { + "name" : "attr1", + "save_value" : { + "save_name" : "attrvalue", + "duplicate_ok" : false + } + } + ] + } + } + ] +} diff --git a/testing/unittests/configmgr/templates/SubstitutionTest-1.json b/testing/unittests/configmgr/templates/SubstitutionTest-1.json index 35b155ada23..fdbd2cff2eb 100644 --- a/testing/unittests/configmgr/templates/SubstitutionTest-1.json +++ b/testing/unittests/configmgr/templates/SubstitutionTest-1.json @@ -2,61 +2,64 @@ "name" : "test", "type" : "modification", "description" : "add a subchild to an existing node in an environment", - "inputs" : [ + "variables" : [ { "name" : "name", "type" : "string", - "value" : "myname" + "values" : ["myname"] }, { "name" : "numsystems", "type" : "string", - "value" : "10" + "values" : ["10"] }, { "name" : "ccnum", "type" : "string", - "value" : "2" + "values" : ["2"] }, { "name" : "hwips", "type" : "iprange", - "value" : "1.2.3.1-10" + "values" : ["1.2.3.1-10"] }, { "name" : "baseip", "type" : "ipaddress", - "value" : "1.2.3.1" + "values" : ["1.2.3.1"] }, { "name" : "systemips", "type" : "iprange", + "user_input" : false, "prepared_value" : "{{baseip}}*{{numsystems}}" } ], "operations" : [ { - "action" : "noop", - "path" : "/Root/Child1", - "count" : "{{numsystems}}", - "attributes" : [ - { - "name" : "name", - "value" : "{{name}}" - }, - { - "name" : "ipaddress", - "value" : "{{hwips}}" - }, - { - "name" : "daliIp", - "value" : "{{hwips[0]}}" - }, - { - "name" : "ccIp", - "value" : "{{hwips[{{numsystems}}-{{ccnum}}]}}" - } - ] + "action" : "modify_node", + "parent_path" : "/Root/Child1", + "data" : { + "count" : "{{numsystems}}", + "attributes" : [ + { + "name" : "name", + "value" : "{{name}}" + }, + { + "name" : "ipaddress", + "value" : "{{hwips}}" + }, + { + "name" : "daliIp", + "value" : "{{hwips[0]}}" + }, + { + "name" : "ccIp", + "value" : "{{hwips[{{numsystems}}-{{ccnum}}]}}" + } + ] + } } ] }