diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index dacf85bb2..278a9b354 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -7,6 +7,7 @@ on: - develop - 'feature/**' - 'release/**' + - 'bugfix/**' pull_request: branches: - main @@ -86,6 +87,7 @@ jobs: git boost openssl + cmake pkg-config libxml2 spdlog @@ -208,32 +210,40 @@ jobs: if: matrix.os == 'ubuntu-22.04' && matrix.ssl == 'OFF' run: > sudo cp /usr/local/etc/uda.socket /usr/local/etc/uda@.service /etc/systemd/system && + sudo chown -R $USER:$USER /usr/local/etc && + echo "export UDA_SERVER_SSL_AUTHENTICATE=OFF" >> /usr/local/etc/udaserver.cfg && sudo systemctl start uda.socket && sudo systemctl enable uda.socket && - sudo chown -R $USER:$USER /usr/local/etc && nc -4zv localhost 56565 && export UDA_HOST=localhost && export UDA_PORT=56565 && - ./build/test/plugins/plugin_test_testplugin + ./build/test/plugins/plugin_test_testplugin && + ctest -V --test-dir build/test/unit_tests --output-on-failure && + uda_cli --help && uda_cli --request "help::help()" - name: Run SSL system tests if: matrix.os == 'ubuntu-22.04' && matrix.ssl == 'ON' - run: > - sudo cp /usr/local/etc/uda.socket /usr/local/etc/uda@.service /etc/systemd/system && - sudo chown -R $USER:$USER /usr/local/etc && - echo "export UDAHOSTNAME=github-ci-ssl" >> /usr/local/etc/udaserver.cfg && - ./scripts/create_certs.sh && - mkdir /usr/local/etc/certs && - cp rootCA.crt server.crt server.key /usr/local/etc/certs && - sudo systemctl start uda.socket && - sudo systemctl enable uda.socket && - nc -4zv localhost 56565 && - export UDA_HOST=localhost && - export UDA_PORT=56565 && - export UDA_CLIENT_SSL_AUTHENTICATE=1 && - export UDA_CLIENT_CA_SSL_CERT=$PWD/rootCA.crt && - export UDA_CLIENT_SSL_CERT=$PWD/client.crt && - export UDA_CLIENT_SSL_KEY=$PWD/client.key && + run: | + sudo cp /usr/local/etc/uda.socket /usr/local/etc/uda@.service /etc/systemd/system + sudo chown -R $USER:$USER /usr/local/etc + # pulls in extra config options from machine.d + echo "export UDAHOSTNAME=github-ci-ssl" >> /usr/local/etc/udaserver.cfg + ./scripts/create_certs.sh + mkdir /usr/local/etc/certs + cp rootCA.crt server.crt server.key /usr/local/etc/certs + sudo systemctl start uda.socket + sudo systemctl enable uda.socket + nc -4zv localhost 56565 + export UDA_HOST=localhost + export UDA_PORT=56565 + export UDA_CLIENT_SSL_AUTHENTICATE=OFF + export UDA_CLIENT_CA_SSL_CERT=$PWD/rootCA.crt + export UDA_CLIENT_SSL_CERT=$PWD/client.crt + export UDA_CLIENT_SSL_KEY=$PWD/client.key + set +e + /usr/local/bin/uda_cli --request "help::help()" && exit 1 || echo "process failed successfully" + set -e + export UDA_CLIENT_SSL_AUTHENTICATE=ON ./build/test/plugins/plugin_test_testplugin # - name: Test diff --git a/source/authentication/udaClientSSL.cpp b/source/authentication/udaClientSSL.cpp index 16aff91ba..ee1845b97 100755 --- a/source/authentication/udaClientSSL.cpp +++ b/source/authentication/udaClientSSL.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -327,7 +328,7 @@ int initUdaClientSSL() // Has the user directly specified SSL/TLS authentication? // Does the connection entry in the client host configuration file have the three SSL authentication files - if (!g_sslProtocol && !getenv("UDA_CLIENT_SSL_AUTHENTICATE")) { + if (!g_sslProtocol && !uda::common::env_config::evaluate_bool_param("UDA_CLIENT_SSL_AUTHENTICATE", false)) { g_sslDisabled = true; if (g_host != nullptr) { diff --git a/source/authentication/udaServerSSL.cpp b/source/authentication/udaServerSSL.cpp index 62ba90ac3..5ba3690b0 100644 --- a/source/authentication/udaServerSSL.cpp +++ b/source/authentication/udaServerSSL.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -280,11 +281,9 @@ int startUdaServerSSL() } // Has the server disabled SSL/TLS authentication? - if (!getenv("UDA_SERVER_SSL_AUTHENTICATE")) { - g_sslDisabled = true; + g_sslDisabled = !uda::common::env_config::evaluate_bool_param("UDA_SERVER_SSL_AUTHENTICATE", false); + if (g_sslDisabled) { return 0; - } else { - g_sslDisabled = false; } UDA_LOG(UDA_LOG_DEBUG, "SSL Authentication is Enabled!\n"); diff --git a/source/bin/uda_cli.cpp b/source/bin/uda_cli.cpp index 6df9b248a..b95014a38 100644 --- a/source/bin/uda_cli.cpp +++ b/source/bin/uda_cli.cpp @@ -444,27 +444,23 @@ void print_result(const uda::Result& res) { void process_request(uda::Client& client, const std::string& request, const std::string& source) { std::cout << "request: " << request << "\n"; - try { - const auto& res = client.get(request, source); - print_result(res); - } catch (const std::exception& ex) { - std::cout << "error: " << ex.what() << "\n"; - } + const auto& res = client.get(request, source); //throws + print_result(res); } void process_batch_requests(uda::Client& client, const std::vector& requests, const std::string& source) { + size_t count = 0; for (const auto& request : requests) { - process_request(client, request, source); + try { + process_request(client, request, source); + count++; + } catch (const std::exception& ex) { + std::cout << "error: " << ex.what() << "\n"; + } + } + if (count == 0 and !requests.empty()) { + throw CLIException("All requests in batch failed"); } - // for (const auto& request : requests) { - // std::cout << "request: " << request << "\n"; - // } - // const auto& res_list = client.get_batch(requests, source); - // - // for (const auto& handle : res_list.handles()) { - // const auto& res = res_list.at(handle); - // print_result(res); - // } } int main(int argc, const char** argv) @@ -487,29 +483,28 @@ int main(int argc, const char** argv) po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm); po::notify(vm); + if (vm.count("help") && vm["help"].as()) { + std::cout << "Usage: " << argv[0] << " [options] request\n"; + std::cout << desc << "\n"; + return 0; + } + conflicting_options(vm, "ping", "request"); conflicting_options(vm, "ping", "batch-file"); conflicting_options(vm, "request", "batch-file"); if (!vm["ping"].as() && vm.count("request") == 0 && vm.count("batch-file") == 0) { throw po::error("either 'ping', 'request' or 'batch-file' must be provided"); } + } catch (const po::unknown_option& err) { + std::cout << "Error: " << err.what() << "\n\n"; + std::cout << "Usage: " << argv[0] << " [options] request\n"; + std::cout << desc << "\n"; + return -1; } catch (po::error& err) { - if (vm["help"].as()) { - std::cout << "Usage: " << argv[0] << " [options] request\n"; - std::cout << desc << "\n"; - return 1; - } else { - std::cout << "Error: " << err.what() << "\n\n"; - std::cout << "Usage: " << argv[0] << " [options] request\n"; - std::cout << desc << "\n"; - return -1; - } - }; - - if (vm["help"].as()) { + std::cout << "Error: " << err.what() << "\n\n"; std::cout << "Usage: " << argv[0] << " [options] request\n"; std::cout << desc << "\n"; - return 1; + return -1; } if (vm.count("host")) { diff --git a/source/common/uda_env_options.hpp b/source/common/uda_env_options.hpp new file mode 100644 index 000000000..9167aaed0 --- /dev/null +++ b/source/common/uda_env_options.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace uda::common::env_config { + +const std::vector truthy_values = {"1", "true", "yes", "on"}; +const std::vector falsey_values = {"0", "false", "no", "off"}; + +inline bool strings_match(std::string_view val, const std::vector& accepted_values) { + std::string value(val); + std::transform(value.begin(), value.end(), value.begin(), + [] (unsigned char c) {return std::tolower(c); }); + + // case insensitive comparison + return std::any_of(accepted_values.begin(), accepted_values.end(), + [&] (std::string v){ + std::transform(v.begin(), v.end(), v.begin(), + [] (unsigned char c) {return std::tolower(c); }); + return value == v; }); +} + +inline bool match_custom_values(std::string_view var_name, const std::vector& accepted_values, + bool default_value=false) { + const char* value = std::getenv(var_name.data()); + if (value == nullptr) { + return default_value; + } + return strings_match(value, accepted_values); +} + +inline std::optional +get_custom_param(std::string_view var_name, const std::vector& accepted_values) { + const char* value = std::getenv(var_name.data()); + if (value == nullptr) { + return {}; + } + if (strings_match(value, accepted_values)) { + return value; + } + return {}; +} + +inline bool evaluate_bool_param(std::string_view var_name, bool default_value=false) { + + const char* val = std::getenv(var_name.data()); + if (val == nullptr) { + return default_value; + } + if (strings_match(val, truthy_values)) { + return true; + } + if (strings_match(val, falsey_values)) { + return false; + } + return default_value; +} + +} // namespace diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1a89a16a9..0cbade343 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -158,7 +158,8 @@ macro( BUILD_TEST NAME SOURCE ) endmacro( BUILD_TEST ) add_subdirectory( plugins ) -add_subdirectory( imas ) +# add_subdirectory( imas ) +add_subdirectory( unit_tests ) add_definitions( -D__USE_XOPEN2K8 ) diff --git a/test/unit_tests/CMakeLists.txt b/test/unit_tests/CMakeLists.txt new file mode 100644 index 000000000..c141b94d6 --- /dev/null +++ b/test/unit_tests/CMakeLists.txt @@ -0,0 +1,10 @@ +Include( FetchContent ) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.8.0 +) + +FetchContent_MakeAvailable( Catch2 ) +add_subdirectory( common ) diff --git a/test/unit_tests/common/CMakeLists.txt b/test/unit_tests/common/CMakeLists.txt new file mode 100644 index 000000000..f7bfd1687 --- /dev/null +++ b/test/unit_tests/common/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable( test_uda_env uda_env_options.test.cpp ) +target_include_directories( test_uda_env PRIVATE ${CMAKE_SOURCE_DIR}/source ) +target_link_libraries( test_uda_env PRIVATE Catch2::Catch2WithMain ) +add_test( NAME TestUdaEnvOptions COMMAND test_uda_env ) diff --git a/test/unit_tests/common/uda_env_options.test.cpp b/test/unit_tests/common/uda_env_options.test.cpp new file mode 100644 index 000000000..13560a2b8 --- /dev/null +++ b/test/unit_tests/common/uda_env_options.test.cpp @@ -0,0 +1,134 @@ +// catch includes +#include +#include + +// std includes +#include +#include +#include + +// uda includes +#include + +namespace { +class EnvFixture +{ + public: + EnvFixture(std::string_view name, std::string_view val) + :name(name), value(val) + { + setenv(this->name.c_str(), value.c_str(), 1); + } + + ~EnvFixture() + { + unsetenv(name.c_str()); + } + + std::string name; + std::string value; +}; +} // anon namespace + +namespace uda_env = uda::common::env_config; + +SCENARIO( "uda options stored in env vars can be interpreted", "[uda-env]" ) +{ + GIVEN( "an existing environment variable" ) + { + AND_GIVEN( "The variable is truthy" ) + { + auto value = GENERATE("1", "true", "yes", "on", "ON", "TRUE", "YeS"); + std::string name = std::string("UDA_ENV_TEST_VAR_EXISTS_") + value; + EnvFixture env(name, value); + + THEN( "check evaluates to true" ) + { + REQUIRE( uda_env::evaluate_bool_param(name.c_str()) ); + } + } + AND_GIVEN( "The variable is falsey" ) + { + auto value = GENERATE("0", "false", "no", "off", "OFF","FALSE", "FaLSe"); + std::string name = std::string("UDA_ENV_TEST_VAR_EXISTS_") + value; + EnvFixture env(name, value); + + THEN( "string match fails for list of truthy values" ) + { + REQUIRE( ! uda_env::match_custom_values(name.c_str(), uda_env::truthy_values) ); + } + THEN( "string match passes for list of falsey values" ) + { + REQUIRE( uda_env::match_custom_values(name.c_str(), uda_env::falsey_values) ); + } + AND_THEN( "check evaluates to false" ) + { + REQUIRE( ! uda_env::evaluate_bool_param(name.c_str()) ); + } + } + AND_GIVEN( "The variable has an unexpected value" ) + { + auto value = GENERATE("fake", "not_in_list", "TYPO"); + std::string name = std::string("UDA_ENV_TEST_VAR_EXISTS_") + value; + EnvFixture env(name, value); + + THEN( "string match fails for lists of both truthy and falsey values" ) + { + REQUIRE( ! uda_env::strings_match(name, uda_env::falsey_values) ); + REQUIRE( ! uda_env::strings_match(name, uda_env::truthy_values) ); + AND_THEN( "check evaluates false" ) + { + REQUIRE( ! uda_env::evaluate_bool_param(name.c_str()) ); + } + } + } + AND_GIVEN( "A custom list of accepted values" ) + { + std::vector accepted_values {"ENUM1", "ENUM2", "ENUM3"}; + + WHEN( "the value is in the accepted list") + { + auto value = GENERATE("ENUM1", "ENUM2", "ENUM3"); + std::string name = std::string("UDA_ENV_TEST_VAR2_EXISTS_") + value; + EnvFixture env(name, value); + THEN( "string match passes for list of accepted values" ) + { + REQUIRE( uda_env::match_custom_values(name.c_str(), accepted_values) ); + } + } + WHEN( "the value is not in the accepted list") + { + auto value = GENERATE("fake", "not_in_list", "TYPO"); + std::string name = std::string("UDA_ENV_TEST_VAR2_EXISTS_") + value; + EnvFixture env(name, value); + THEN( "string match fails for list of accepted values" ) + { + REQUIRE( ! uda_env::match_custom_values(name.c_str(), accepted_values) ); + } + } + } + } + + GIVEN( "a non-existent environment variable" ) + { + const char* name = "UDA_ENV_TEST_VAR_UNSET"; + unsetenv(name); + WHEN( "A default value of true is used" ) + { + bool default_value = true; + THEN( "check evaluates true" ) + { + REQUIRE( uda_env::evaluate_bool_param(name, default_value) ); + } + } + WHEN( "A default value of false is used" ) + { + bool default_value = false; + THEN( "check evaluates false" ) + { + REQUIRE( ! uda_env::evaluate_bool_param(name, default_value) ); + } + } + + } +}