This is an automated email from the ASF dual-hosted git repository. fgerlits pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit 1dfab222d44100186e3450cd0fc964f04708734b Author: Gabor Gyimesi <gamezb...@gmail.com> AuthorDate: Tue Aug 22 15:08:27 2023 +0200 MINIFICPP-1774 Set properties from command line arguments Signed-off-by: Ferenc Gerlits <fgerl...@gmail.com> This closes #1638 --- CMakeLists.txt | 7 +- LICENSE | 32 ++----- NOTICE | 1 + cmake/{CxxOpts.cmake => ArgParse.cmake} | 19 +--- controller/CMakeLists.txt | 6 +- controller/MiNiFiController.cpp | 140 ++++++++++++++++----------- encrypt-config/ArgParser.cpp | 164 -------------------------------- encrypt-config/ArgParser.h | 74 -------------- encrypt-config/CMakeLists.txt | 3 +- encrypt-config/CommandException.h | 36 ------- encrypt-config/EncryptConfigMain.cpp | 33 ++++--- extensions/ExtensionHeader.txt | 2 +- libminifi/CMakeLists.txt | 6 +- minifi_main/CMakeLists.txt | 6 +- minifi_main/MiNiFiMain.cpp | 144 +++++++++++++++++++--------- nanofi/CMakeLists.txt | 2 +- nanofi/ecu/CMakeLists.txt | 2 +- nanofi/examples/CMakeLists.txt | 2 +- 18 files changed, 236 insertions(+), 443 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f602449c3..e74e7ed0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,9 +20,7 @@ cmake_minimum_required(VERSION 3.24) cmake_policy(SET CMP0096 NEW) # policy to preserve the leading zeros in PROJECT_VERSION_{MAJOR,MINOR,PATCH,TWEAK} cmake_policy(SET CMP0065 OLD) # default export policy, required for self-dlopen -if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) - cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted contents to the time of extraction -endif() +cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted contents to the time of extraction project(nifi-minifi-cpp VERSION 0.15.0) set(PROJECT_NAME "nifi-minifi-cpp") @@ -296,9 +294,6 @@ add_library(RapidJSON INTERFACE) target_include_directories(RapidJSON SYSTEM INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/rapidjson-48fbd8cd202ca54031fe799db2ad44ffa8e77c13/include") target_compile_definitions(RapidJSON INTERFACE RAPIDJSON_HAS_STDSTRING) -# cxxopts -include(CxxOpts) - include(Coroutines) enable_coroutines() diff --git a/LICENSE b/LICENSE index 82cee2dcf..6d2a0d4f7 100644 --- a/LICENSE +++ b/LICENSE @@ -1250,28 +1250,6 @@ Redistribution and use in source and binary forms, with or without modification, THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFI [...] -This product bundles 'cxxopts' which is available under an MIT license. - -Copyright (c) 2014 Jarryd Beck - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - This product bundles zlib which is available under the zlib license: (C) 1995-2017 Jean-loup Gailly and Mark Adler @@ -3416,3 +3394,13 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +This product bundles 'argparse' which is available under The MIT License. + +Copyright (c) 2018 Pranav Srinivas Kumar <pranav.srinivas.ku...@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/NOTICE b/NOTICE index 006fa971f..4e889868c 100644 --- a/NOTICE +++ b/NOTICE @@ -76,6 +76,7 @@ This software includes third party software subject to the following copyrights: - OpenSSL - Copyright (c) 1998-2022 The OpenSSL Project, Copyright (c) 1995-1998 Eric A. Young, Tim J. Hudson. All rights reserved. - bitwizeshift.github.io - Copyright (c) 2020 Matthew Rodusek - magic_enum - Copyright (c) 2019 - 2023 Daniil Goncharov +- argparse - Copyright (c) 2018 Pranav Srinivas Kumar <pranav.srinivas.ku...@gmail.com> The licenses for these third party components are included in LICENSE.txt diff --git a/cmake/CxxOpts.cmake b/cmake/ArgParse.cmake similarity index 53% rename from cmake/CxxOpts.cmake rename to cmake/ArgParse.cmake index c6ad00364..883f34986 100644 --- a/cmake/CxxOpts.cmake +++ b/cmake/ArgParse.cmake @@ -16,18 +16,9 @@ # under the License. include(FetchContent) - -FetchContent_Declare(cxxopts_src - URL https://github.com/jarro2783/cxxopts/archive/refs/tags/v2.2.1.tar.gz - URL_HASH SHA256=984aa3c8917d649b14d7f6277104ce38dd142ce378a9198ec926f03302399681 +FetchContent_Declare( + argparse + URL https://github.com/p-ranav/argparse/archive/refs/tags/v2.9.tar.gz + URL_HASH SHA256=cd563293580b9dc592254df35b49cf8a19b4870ff5f611c7584cf967d9e6031e ) -FetchContent_GetProperties(cxxopts_src) -if (NOT cxxopts_src_POPULATED) - FetchContent_Populate(cxxopts_src) - set(CXXOPTS_INCLUDE_DIR "${cxxopts_src_SOURCE_DIR}/include" CACHE STRING "" FORCE) - add_library(cxxopts INTERFACE) - add_library(cxxopts::cxxopts ALIAS cxxopts) - target_sources(cxxopts INTERFACE ${CXXOPTS_INCLUDE_DIR}/cxxopts.hpp) - target_include_directories(cxxopts SYSTEM INTERFACE ${CXXOPTS_INCLUDE_DIR}) - target_compile_features(cxxopts INTERFACE cxx_std_11) -endif() +FetchContent_MakeAvailable(argparse) diff --git a/controller/CMakeLists.txt b/controller/CMakeLists.txt index dd3161198..5ad98fbd4 100644 --- a/controller/CMakeLists.txt +++ b/controller/CMakeLists.txt @@ -17,7 +17,8 @@ # under the License. # -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.24) +cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted contents to the time of extraction include_directories(../minifi_main/ ../libminifi/include ../libminifi/include/c2 ../libminifi/include/c2/protocols/ ../libminifi/include/core/state ./libminifi/include/core/statemanagement/metrics ../libminifi/include/core/yaml ../libminifi/include/core) @@ -39,7 +40,8 @@ if (WIN32) endif() add_executable(minificontroller ${MINIFI_CONTROLLER_SOURCES}) -target_link_libraries(minificontroller ${LIBMINIFI} cxxopts Threads::Threads) +include(ArgParse) +target_link_libraries(minificontroller ${LIBMINIFI} argparse Threads::Threads) set_target_properties(minificontroller PROPERTIES OUTPUT_NAME minificontroller diff --git a/controller/MiNiFiController.cpp b/controller/MiNiFiController.cpp index 053a5fbae..30930f676 100644 --- a/controller/MiNiFiController.cpp +++ b/controller/MiNiFiController.cpp @@ -17,6 +17,8 @@ */ #include <vector> #include <iostream> +#include <string> +#include <string_view> #include "MainHelper.h" #include "properties/Configure.h" @@ -26,8 +28,9 @@ #include "core/extension/ExtensionManager.h" #include "core/ConfigurationFactory.h" #include "Exception.h" - -#include "cxxopts.hpp" +#include "argparse/argparse.hpp" +#include "range/v3/algorithm/contains.hpp" +#include "agent/agent_version.h" namespace minifi = org::apache::nifi::minifi; @@ -106,61 +109,89 @@ int main(int argc, char **argv) { socket_data.ssl_context_service = getSSLContextService(configuration); } catch(const minifi::Exception& ex) { logger->log_error(ex.what()); - exit(1); + std::exit(1); } - cxxopts::Options options("MiNiFiController", "MiNiFi local agent controller"); - options.positional_help("[optional args]").show_positional_help(); - - options.add_options() - ("h,help", "Shows Help") - ("host", "Specifies connecting host name", cxxopts::value<std::string>()) - ("port", "Specifies connecting host port", cxxopts::value<int>()) - ("stop", "Shuts down the provided component", cxxopts::value<std::vector<std::string>>()) - ("start", "Starts provided component", cxxopts::value<std::vector<std::string>>()) - ("l,list", "Provides a list of connections or processors", cxxopts::value<std::string>()) - ("c,clear", "Clears the associated connection queue", cxxopts::value<std::vector<std::string>>()) - ("getsize", "Reports the size of the associated connection queue", cxxopts::value<std::vector<std::string>>()) - ("updateflow", "Updates the flow of the agent using the provided flow file", cxxopts::value<std::string>()) - ("getfull", "Reports a list of full connections") - ("jstack", "Returns backtraces from the agent") - ("manifest", "Generates a manifest for the current binary") - ("noheaders", "Removes headers from output streams"); + argparse::ArgumentParser argument_parser("Apache MiNiFi C++ Controller", minifi::AgentBuild::VERSION); + argument_parser.add_argument("--host").metavar("HOSTNAME") + .help("Specifies connecting host name"); + argument_parser.add_argument("--port") + .metavar("PORT") + .help("Specifies connecting host port") + .scan<'d', int>(); + argument_parser.add_argument("--stop") + .metavar("COMPONENT") + .nargs(argparse::nargs_pattern::at_least_one) + .help("Shuts down the provided components"); + argument_parser.add_argument("--start") + .metavar("COMPONENT") + .nargs(argparse::nargs_pattern::at_least_one) + .help("Starts provided components"); + argument_parser.add_argument("-l", "--list") + .action([](const std::string& value) { + if (ranges::contains(std::array{"components", "connections"}, value)) { + return value; + } + throw std::runtime_error("List command only accepts the following parameters: [components, connections]"); + }) + .help("Provides a list of connections or components (processors). Accepted parameters: [components, components]"); + argument_parser.add_argument("-c", "--clear") + .metavar("CONNECTION") + .nargs(argparse::nargs_pattern::at_least_one) + .help("Clears the associated connection queues"); + argument_parser.add_argument("--getsize") + .metavar("CONNECTION") + .nargs(argparse::nargs_pattern::at_least_one) + .help("Reports the size of the associated connection queues"); + argument_parser.add_argument("--updateflow") + .metavar("FLOW_CONFIG_PATH") + .help("Updates the flow of the agent using the provided flow file"); + + auto addFlagOption = [&](std::string_view name, const std::string& help) { + argument_parser.add_argument(name) + .default_value(false) + .implicit_value(true) + .help(help); + }; + addFlagOption("--getfull", "Reports a list of full connections"); + addFlagOption("--jstack", "Returns backtraces from the agent"); + addFlagOption("--manifest", "Generates a manifest for the current binary"); + addFlagOption("--noheaders", "Removes headers from output streams"); bool show_headers = true; try { - auto result = options.parse(argc, argv); - - if (result.count("help")) { - std::cout << options.help({ "", "Group" }) << std::endl; - exit(0); - } + argument_parser.parse_args(argc, argv); + } catch (const std::runtime_error& err) { + std::cerr << err.what() << std::endl; + std::cerr << argument_parser; + std::exit(1); + } - if (result.count("host")) { - socket_data.host = result["host"].as<std::string>(); + try { + if (const auto& host = argument_parser.present("--host")) { + socket_data.host = *host; } else { configuration->get(minifi::Configure::controller_socket_host, socket_data.host); } std::string port_str; - if (result.count("port")) { - socket_data.port = result["port"].as<int>(); + if (const auto& port = argument_parser.present<int>("--port")) { + socket_data.port = *port; } else if (socket_data.port == -1 && configuration->get(minifi::Configure::controller_socket_port, port_str)) { socket_data.port = std::stoi(port_str); } if ((minifi::IsNullOrEmpty(socket_data.host) && socket_data.port == -1)) { std::cout << "MiNiFi Controller is disabled" << std::endl; - exit(0); + std::exit(0); } - if (result.count("noheaders")) { + if (argument_parser.get<bool>("--noheaders")) { show_headers = false; } - if (result.count("stop") > 0) { - auto& components = result["stop"].as<std::vector<std::string>>(); - for (const auto& component : components) { + if (const auto& components = argument_parser.present<std::vector<std::string>>("--stop")) { + for (const auto& component : *components) { if (minifi::controller::stopComponent(socket_data, component)) std::cout << component << " requested to stop" << std::endl; else @@ -168,9 +199,8 @@ int main(int argc, char **argv) { } } - if (result.count("start") > 0) { - auto& components = result["start"].as<std::vector<std::string>>(); - for (const auto& component : components) { + if (const auto& components = argument_parser.present<std::vector<std::string>>("--start")) { + for (const auto& component : *components) { if (minifi::controller::startComponent(socket_data, component)) std::cout << component << " requested to start" << std::endl; else @@ -178,9 +208,8 @@ int main(int argc, char **argv) { } } - if (result.count("c") > 0) { - auto& components = result["c"].as<std::vector<std::string>>(); - for (const auto& connection : components) { + if (const auto& components = argument_parser.present<std::vector<std::string>>("--clear")) { + for (const auto& connection : *components) { if (minifi::controller::clearConnection(socket_data, connection)) { std::cout << "Sent clear command to " << connection << "." << std::endl; } else { @@ -189,40 +218,39 @@ int main(int argc, char **argv) { } } - if (result.count("getsize") > 0) { - auto& components = result["getsize"].as<std::vector<std::string>>(); - for (const auto& component : components) { + if (const auto& components = argument_parser.present<std::vector<std::string>>("--getsize")) { + for (const auto& component : *components) { if (!minifi::controller::getConnectionSize(socket_data, std::cout, component)) std::cout << "Could not connect to remote host " << socket_data.host << ":" << socket_data.port << std::endl; } } - if (result.count("l") > 0) { - auto& option = result["l"].as<std::string>(); - if (option == "components") { + + if (const auto& option = argument_parser.present("--list")) { + if (*option == "components") { if (!minifi::controller::listComponents(socket_data, std::cout, show_headers)) std::cout << "Could not connect to remote host " << socket_data.host << ":" << socket_data.port << std::endl; - } else if (option == "connections") { + } else if (*option == "connections") { if (!minifi::controller::listConnections(socket_data, std::cout, show_headers)) std::cout << "Could not connect to remote host " << socket_data.host << ":" << socket_data.port << std::endl; } } - if (result.count("getfull") > 0) { + + if (argument_parser.get<bool>("--getfull")) { if (!minifi::controller::getFullConnections(socket_data, std::cout)) std::cout << "Could not connect to remote host " << socket_data.host << ":" << socket_data.port << std::endl; } - if (result.count("updateflow") > 0) { - auto& flow_file = result["updateflow"].as<std::string>(); - if (!minifi::controller::updateFlow(socket_data, std::cout, flow_file)) + if (const auto& flow_file = argument_parser.present("--updateflow")) { + if (!minifi::controller::updateFlow(socket_data, std::cout, *flow_file)) std::cout << "Could not connect to remote host " << socket_data.host << ":" << socket_data.port << std::endl; } - if (result.count("manifest") > 0) { + if (argument_parser.get<bool>("--manifest")) { if (!minifi::controller::printManifest(socket_data, std::cout)) std::cout << "Could not connect to remote host " << socket_data.host << ":" << socket_data.port << std::endl; } - if (result.count("jstack") > 0) { + if (argument_parser.get<bool>("--jstack")) { if (!minifi::controller::getJstacks(socket_data, std::cout)) std::cout << "Could not connect to remote host " << socket_data.host << ":" << socket_data.port << std::endl; } @@ -230,8 +258,8 @@ int main(int argc, char **argv) { // catch anything thrown within try block that derives from std::exception std::cerr << exc.what() << std::endl; } catch (...) { - std::cout << options.help({ "", "Group" }) << std::endl; - exit(0); + std::cerr << argument_parser; + std::exit(1); } return 0; } diff --git a/encrypt-config/ArgParser.cpp b/encrypt-config/ArgParser.cpp deleted file mode 100644 index 5a1a2839d..000000000 --- a/encrypt-config/ArgParser.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 <string> -#include <set> -#include <iostream> -#include <algorithm> -#include "ArgParser.h" -#include "utils/OptionalUtils.h" -#include "utils/StringUtils.h" -#include "utils/CollectionUtils.h" -#include "CommandException.h" - -namespace org::apache::nifi::minifi::encrypt_config { - -const std::vector<Argument> Arguments::registered_args_{ - {std::set<std::string>{"--minifi-home", "-m"}, - true, - "minifi home", - "Specifies the home directory used by the minifi agent"} -}; - -const std::vector<Flag> Arguments::registered_flags_{ - {std::set<std::string>{"--help", "-h"}, - "Prints this help message"}, - {std::set<std::string>{"--encrypt-flow-config"}, - "If set, the flow configuration file (as specified in minifi.properties) is also encrypted."} -}; - -std::string Arguments::getHelp() { - std::stringstream ss; - ss << "Usage: " << "encrypt-config"; - for (const auto& arg : registered_args_) { - ss << " "; - if (!arg.required) { - ss << "["; - } - ss << utils::StringUtils::join("|", arg.names) - << " <" << arg.value_name << ">"; - if (!arg.required) { - ss << "]"; - } - } - for (const auto& flag : registered_flags_) { - ss << " [" << utils::StringUtils::join("|", flag.names) << "]"; - } - ss << "\n"; - for (const auto& arg : registered_args_) { - ss << "\t"; - ss << utils::StringUtils::join("|", arg.names) << " : "; - if (arg.required) { - ss << "(required)"; - } else { - ss << "(optional)"; - } - ss << " " << arg.description; - ss << "\n"; - } - for (const auto& flag : registered_flags_) { - ss << "\t" << utils::StringUtils::join("|", flag.names) << " : " - << flag.description << "\n"; - } - return ss.str(); -} - -void Arguments::set(const std::string& key, const std::string& value) { - if (get(key)) { - throw CommandException("Key is specified more than once \"" + key + "\""); - } - args_[key] = value; -} - -void Arguments::set(const std::string& flag) { - if (isSet(flag)) { - throw CommandException("Flag is specified more than once \"" + flag + "\""); - } - flags_.insert(flag); -} - -std::optional<std::string> Arguments::get(const std::string &key) const { - return getArg(key) | utils::flatMap([&] (const Argument& arg) {return get(arg);}); -} - -std::optional<std::string> Arguments::get(const Argument& arg) const { - for (const auto& name : arg.names) { - auto it = args_.find(name); - if (it != args_.end()) { - return it->second; - } - } - return {}; -} - -bool Arguments::isSet(const std::string &flag) const { - std::optional<Flag> opt_flag = getFlag(flag); - if (!opt_flag) { - return false; - } - return utils::haveCommonItem(opt_flag->names, flags_); -} - -Arguments Arguments::parse(int argc, char* argv[]) { - Arguments args; - for (int argIdx = 1; argIdx < argc; ++argIdx) { - std::string key{argv[argIdx]}; - if (getFlag(key)) { - args.set(key); - continue; - } - if (!getArg(key)) { - throw CommandException("Unrecognized option: \"" + key + "\""); - } - if (argIdx == argc - 1) { - throw CommandException("No value specified for key \"" + key + "\""); - } - ++argIdx; - std::string value{argv[argIdx]}; - args.set(key, value); - } - if (args.isSet("-h")) { - std::cout << getHelp(); - std::exit(0); - } - for (const auto& arg : registered_args_) { - if (arg.required && !args.get(arg)) { - throw CommandException("Missing required option " + utils::StringUtils::join("|", arg.names)); - } - } - return args; -} - -std::optional<Flag> Arguments::getFlag(const std::string &name) { - for (const auto& flag : registered_flags_) { - if (flag.names.contains(name)) { - return flag; - } - } - return {}; -} - -std::optional<Argument> Arguments::getArg(const std::string &key) { - for (const auto& arg : registered_args_) { - if (arg.names.contains(key)) { - return arg; - } - } - return {}; -} - -} // namespace org::apache::nifi::minifi::encrypt_config diff --git a/encrypt-config/ArgParser.h b/encrypt-config/ArgParser.h deleted file mode 100644 index fda3570d8..000000000 --- a/encrypt-config/ArgParser.h +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include <string> -#include <set> -#include <vector> -#include <map> -#include <optional> - -namespace org { -namespace apache { -namespace nifi { -namespace minifi { -namespace encrypt_config { - -struct Argument { - std::set<std::string> names; - bool required; - std::string value_name; - std::string description; -}; - -struct Flag { - std::set<std::string> names; - std::string description; -}; - -class Arguments { - static const std::vector<Argument> registered_args_; - static const std::vector<Flag> registered_flags_; - - void set(const std::string& key, const std::string& value); - - void set(const std::string& flag); - - static std::optional<Argument> getArg(const std::string& key); - static std::optional<Flag> getFlag(const std::string& name); - - public: - static Arguments parse(int argc, char* argv[]); - - static std::string getHelp(); - - [[nodiscard]] std::optional<std::string> get(const std::string& key) const; - - [[nodiscard]] bool isSet(const std::string& flag) const; - - private: - [[nodiscard]] std::optional<std::string> get(const Argument& arg) const; - - std::map<std::string, std::string> args_; - std::set<std::string> flags_; -}; - -} // namespace encrypt_config -} // namespace minifi -} // namespace nifi -} // namespace apache -} // namespace org diff --git a/encrypt-config/CMakeLists.txt b/encrypt-config/CMakeLists.txt index d54b4f06a..e3c490d61 100644 --- a/encrypt-config/CMakeLists.txt +++ b/encrypt-config/CMakeLists.txt @@ -25,7 +25,8 @@ if (WIN32) endif() add_executable(encrypt-config "${ENCRYPT_CONFIG_FILES}") target_include_directories(encrypt-config PRIVATE ../libminifi/include) -target_link_libraries(encrypt-config libsodium ${LIBMINIFI}) +include(ArgParse) +target_link_libraries(encrypt-config libsodium argparse ${LIBMINIFI}) set_target_properties(encrypt-config PROPERTIES OUTPUT_NAME encrypt-config) set_target_properties(encrypt-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") diff --git a/encrypt-config/CommandException.h b/encrypt-config/CommandException.h deleted file mode 100644 index 1de4494ca..000000000 --- a/encrypt-config/CommandException.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include <exception> -#include <string> - -namespace org { -namespace apache { -namespace nifi { -namespace minifi { -namespace encrypt_config { - -struct CommandException : std::logic_error { - using std::logic_error::logic_error; -}; - -} // namespace encrypt_config -} // namespace minifi -} // namespace nifi -} // namespace apache -} // namespace org diff --git a/encrypt-config/EncryptConfigMain.cpp b/encrypt-config/EncryptConfigMain.cpp index ae593037d..34760781b 100644 --- a/encrypt-config/EncryptConfigMain.cpp +++ b/encrypt-config/EncryptConfigMain.cpp @@ -19,18 +19,33 @@ #include <typeinfo> #include "EncryptConfig.h" -#include "ArgParser.h" -#include "CommandException.h" +#include "argparse/argparse.hpp" +#include "agent/agent_version.h" -using org::apache::nifi::minifi::encrypt_config::Arguments; using org::apache::nifi::minifi::encrypt_config::EncryptConfig; -using org::apache::nifi::minifi::encrypt_config::CommandException; int main(int argc, char* argv[]) try { - Arguments args = Arguments::parse(argc, argv); - EncryptConfig encrypt_config{args.get("-m").value()}; + argparse::ArgumentParser argument_parser("Apache MiNiFi C++ Encrypt-Config", org::apache::nifi::minifi::AgentBuild::VERSION); + argument_parser.add_argument("-m", "--minifi-home") + .required() + .metavar("MINIFI_HOME") + .help("Specifies the home directory used by the minifi agent"); + argument_parser.add_argument("-e", "--encrypt-flow-config") + .default_value(false) + .implicit_value(true) + .help("If set, the flow configuration file (as specified in minifi.properties) is also encrypted."); + + try { + argument_parser.parse_args(argc, argv); + } catch (const std::runtime_error& err) { + std::cerr << err.what() << std::endl; + std::cerr << argument_parser; + std::exit(1); + } + + EncryptConfig encrypt_config{argument_parser.get("-m")}; EncryptConfig::EncryptionType type = encrypt_config.encryptSensitiveProperties(); - if (args.isSet("--encrypt-flow-config")) { + if (argument_parser.get<bool>("--encrypt-flow-config")) { encrypt_config.encryptFlowConfig(); } else if (type == EncryptConfig::EncryptionType::RE_ENCRYPT) { std::cout << "WARNING: you did not request the flow config to be updated, " @@ -38,10 +53,6 @@ int main(int argc, char* argv[]) try { << "you won't be able to recover the flow config.\n"; } return 0; -} catch (const CommandException& c_ex) { - std::cerr << c_ex.what() << "\n"; - std::cerr << Arguments::getHelp() << "\n"; - return 1; } catch (const std::exception& ex) { std::cerr << ex.what() << "\n(" << typeid(ex).name() << ")\n"; return 2; diff --git a/extensions/ExtensionHeader.txt b/extensions/ExtensionHeader.txt index db240d100..9a9da14b4 100644 --- a/extensions/ExtensionHeader.txt +++ b/extensions/ExtensionHeader.txt @@ -17,7 +17,7 @@ # under the License. # -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.24) include_directories(../../libminifi/include ../../libminifi/include/core) diff --git a/libminifi/CMakeLists.txt b/libminifi/CMakeLists.txt index ca10f5ca0..2a7e581b7 100644 --- a/libminifi/CMakeLists.txt +++ b/libminifi/CMakeLists.txt @@ -17,11 +17,9 @@ # under the License. # -cmake_minimum_required (VERSION 3.16) +cmake_minimum_required (VERSION 3.24) cmake_policy(SET CMP0096 NEW) # policy to preserve the leading zeros in PROJECT_VERSION_{MAJOR,MINOR,PATCH,TWEAK} -if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) - cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted contents to the time of extraction -endif() +cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted contents to the time of extraction project(nifi-libcore-minifi VERSION 0.15.0) set(PROJECT_NAME "nifi-libcore-minifi") diff --git a/minifi_main/CMakeLists.txt b/minifi_main/CMakeLists.txt index c1e5e80d2..b80cdbf33 100644 --- a/minifi_main/CMakeLists.txt +++ b/minifi_main/CMakeLists.txt @@ -17,7 +17,8 @@ # under the License. # -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.24) +cmake_policy(SET CMP0135 NEW) # policy to set the timestamps of extracted contents to the time of extraction include_directories(../libminifi/include) @@ -63,7 +64,8 @@ if (WIN32) endif() get_property(extensions GLOBAL PROPERTY EXTENSION-OPTIONS) -target_link_libraries(minifiexe spdlog libsodium gsl-lite ${LIBMINIFI}) +include(ArgParse) +target_link_libraries(minifiexe spdlog libsodium gsl-lite argparse ${LIBMINIFI}) set_target_properties(minifiexe PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") set_target_properties(minifiexe PROPERTIES OUTPUT_NAME minifi) diff --git a/minifi_main/MiNiFiMain.cpp b/minifi_main/MiNiFiMain.cpp index e9bda89ac..56fa28bc4 100644 --- a/minifi_main/MiNiFiMain.cpp +++ b/minifi_main/MiNiFiMain.cpp @@ -71,6 +71,8 @@ #include "c2/C2Agent.h" #include "core/state/MetricsPublisherFactory.h" #include "core/state/MetricsPublisherStore.h" +#include "argparse/argparse.hpp" +#include "agent/agent_version.h" namespace minifi = org::apache::nifi::minifi; namespace core = minifi::core; @@ -134,7 +136,95 @@ void writeJsonSchema(const std::shared_ptr<minifi::Configure> &configuration, st out << minifi::docs::generateJsonSchema(); } +void overridePropertiesFromCommandLine(const argparse::ArgumentParser& parser, const std::shared_ptr<minifi::Configure>& configure) { + const auto& properties = parser.get<std::vector<std::string>>("--property"); + for (const auto& property : properties) { + auto property_key_and_value = utils::StringUtils::splitAndTrimRemovingEmpty(property, "="); + if (property_key_and_value.size() != 2) { + std::cerr << "Command line property must be defined in <key>=<value> format, invalid property: " << property << std::endl; + std::cerr << parser; + std::exit(1); + } + configure->set(property_key_and_value[0], property_key_and_value[1]); + } +} + +void dumpDocsIfRequested(const argparse::ArgumentParser& parser, const std::shared_ptr<minifi::Configure>& configure) { + if (!parser.is_used("--docs")) { + return; + } + const auto& docs_params = parser.get<std::vector<std::string>>("--docs"); + if (utils::file::create_dir(docs_params[0]) != 0) { + std::cerr << "Working directory doesn't exist and cannot be created: " << docs_params[0] << std::endl; + std::exit(1); + } + + std::cout << "Dumping docs to " << docs_params[0] << std::endl; + if (docs_params.size() > 1) { + auto path = std::filesystem::path(docs_params[1]); + if (std::filesystem::exists(path) && !std::filesystem::is_regular_file(path)) { + std::cerr << "PROCESSORS.md target path exists, but it is not a regular file: " << path << std::endl; + std::exit(1); + } + auto dir = path.parent_path(); + if (dir == docs_params[0]) { + std::cerr << "Target file should be out of the working directory: " << dir << std::endl; + std::exit(1); + } + std::ofstream outref(docs_params[1]); + dumpDocs(configure, docs_params[0], outref); + } else { + dumpDocs(configure, docs_params[0], std::cout); + } + std::exit(0); +} + +void writeSchemaIfRequested(const argparse::ArgumentParser& parser, const std::shared_ptr<minifi::Configure>& configure) { + if (!parser.is_used("--schema")) { + return; + } + const auto& schema_path = parser.get("--schema"); + if (std::filesystem::exists(schema_path) && !std::filesystem::is_regular_file(schema_path)) { + std::cerr << "JSON schema target path already exists, but it is not a regular file: " << schema_path << std::endl; + std::exit(1); + } + + auto parent_dir = std::filesystem::path(schema_path).parent_path(); + if (utils::file::create_dir(parent_dir) != 0) { + std::cerr << "JSON schema parent directory doesn't exist and cannot be created: " << parent_dir << std::endl; + std::exit(1); + } + std::cout << "Writing json schema to " << schema_path << std::endl; + { + std::ofstream schema_file{schema_path}; + writeJsonSchema(configure, schema_file); + } + std::exit(0); +} + int main(int argc, char **argv) { + argparse::ArgumentParser argument_parser("Apache MiNiFi C++", minifi::AgentBuild::VERSION); + argument_parser.add_argument("-p", "--property") + .append() + .metavar("KEY=VALUE") + .help("Override a property read from minifi.properties file in key=value format"); + argument_parser.add_argument("-d", "--docs") + .nargs(1, 2) + .metavar("PATH") + .help("Generate documentation in the directory specified in the first parameter. File path can be specified for the PROCESSORS.md file in the second parameter. " + "If no separate path is given for the PROCESSORS.md file, it will be printed to stdout."); + argument_parser.add_argument("-s", "--schema") + .metavar("PATH") + .help("Generate JSON schema to the specified path"); + + try { + argument_parser.parse_args(argc, argv); + } catch (const std::runtime_error& err) { + std::cerr << err.what() << std::endl; + std::cerr << argument_parser; + std::exit(1); + } + #ifdef WIN32 RunAsServiceIfNeeded(); @@ -257,64 +347,24 @@ int main(int argc, char **argv) { const std::shared_ptr<minifi::Configure> configure = std::make_shared<minifi::Configure>(std::move(decryptor), std::move(log_properties)); configure->setHome(minifiHome); configure->loadConfigureFile(DEFAULT_NIFI_PROPERTIES_FILE); + overridePropertiesFromCommandLine(argument_parser, configure); minifi::core::extension::ExtensionManager::get().initialize(configure); - if (argc >= 2 && std::string("docs") == argv[1]) { - if (argc < 3 || argc > 4) { - std::cerr << "Usage: <minifiexe> docs <directory where to write individual doc files> [file where to write PROCESSORS.md]\n"; - std::cerr << " If no file name is given for PROCESSORS.md, it will be printed to stdout.\n"; - exit(1); - } - if (utils::file::create_dir(argv[2]) != 0) { - std::cerr << "Working directory doesn't exist and cannot be created: " << argv[2] << std::endl; - exit(1); - } - - std::cout << "Dumping docs to " << argv[2] << std::endl; - if (argc == 4) { - auto path = std::filesystem::path(argv[3]); - auto dir = path.parent_path(); - auto filename = path.filename(); - if (dir == argv[2]) { - std::cerr << "Target file should be out of the working directory: " << dir << std::endl; - exit(1); - } - std::ofstream outref(argv[3]); - dumpDocs(configure, argv[2], outref); - } else { - dumpDocs(configure, argv[2], std::cout); - } - exit(0); - } - - if (argc >= 2 && std::string("schema") == argv[1]) { - if (argc != 3) { - std::cerr << "Malformed schema command, expected '<minifiexe> schema <output-file>'" << std::endl; - std::exit(1); - } - - std::cout << "Writing json schema to " << argv[2] << std::endl; - - { - std::ofstream schema_file{argv[2]}; - writeJsonSchema(configure, schema_file); - } - std::exit(0); - } + dumpDocsIfRequested(argument_parser, configure); + writeSchemaIfRequested(argument_parser, configure); std::chrono::milliseconds stop_wait_time = configure->get(minifi::Configure::nifi_graceful_shutdown_seconds) | utils::flatMap(utils::timeutils::StringToDuration<std::chrono::milliseconds>) | utils::valueOrElse([] { return std::chrono::milliseconds(STOP_WAIT_TIME_MS);}); - configure->get(minifi::Configure::nifi_provenance_repository_class_name, prov_repo_class); // Create repos for flow record and provenance std::shared_ptr prov_repo = core::createRepository(prov_repo_class, "provenance"); if (!prov_repo || !prov_repo->initialize(configure)) { logger->log_error("Provenance repository failed to initialize, exiting.."); - exit(1); + std::exit(1); } configure->get(minifi::Configure::nifi_flow_repository_class_name, flow_repo_class); @@ -323,7 +373,7 @@ int main(int argc, char **argv) { if (!flow_repo || !flow_repo->initialize(configure)) { logger->log_error("Flow file repository failed to initialize, exiting.."); - exit(1); + std::exit(1); } configure->get(minifi::Configure::nifi_content_repository_class_name, content_repo_class); @@ -332,13 +382,13 @@ int main(int argc, char **argv) { if (!content_repo->initialize(configure)) { logger->log_error("Content repository failed to initialize, exiting.."); - exit(1); + std::exit(1); } const bool is_flow_repo_non_persistent = flow_repo->isNoop() || std::dynamic_pointer_cast<core::repository::VolatileFlowFileRepository>(flow_repo) != nullptr; const bool is_content_repo_non_persistent = std::dynamic_pointer_cast<core::repository::VolatileContentRepository>(content_repo) != nullptr; if (is_flow_repo_non_persistent != is_content_repo_non_persistent) { logger->log_error("Both or neither of flowfile and content repositories must be persistent! Exiting.."); - exit(1); + std::exit(1); } std::string content_repo_path; diff --git a/nanofi/CMakeLists.txt b/nanofi/CMakeLists.txt index e302b89ad..d832150d8 100644 --- a/nanofi/CMakeLists.txt +++ b/nanofi/CMakeLists.txt @@ -17,7 +17,7 @@ # under the License. # -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.24) cmake_policy(SET CMP0096 NEW) # policy to preserve the leading zeros in PROJECT_VERSION_{MAJOR,MINOR,PATCH,TWEAK} include_directories(include) diff --git a/nanofi/ecu/CMakeLists.txt b/nanofi/ecu/CMakeLists.txt index 8973fae5f..8f077e0d9 100644 --- a/nanofi/ecu/CMakeLists.txt +++ b/nanofi/ecu/CMakeLists.txt @@ -17,7 +17,7 @@ # under the License. # -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.24) if (NOT WIN32) add_executable(log_aggregator log_aggregator.c) diff --git a/nanofi/examples/CMakeLists.txt b/nanofi/examples/CMakeLists.txt index 24855e930..fbd9aefec 100644 --- a/nanofi/examples/CMakeLists.txt +++ b/nanofi/examples/CMakeLists.txt @@ -17,7 +17,7 @@ # under the License. # -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.24) include(CppVersion) set_cpp_version()