From c577554073c6b4afc65bce3f51debda0f60a1415 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Tue, 5 Jun 2018 13:43:10 +0200 Subject: [PATCH] Implement support for the --env command-line argument --- icinga-app/icinga.cpp | 103 +++++++++++++++++++++++- icinga-installer/icinga2.wixpatch.cmake | 6 +- lib/cli/apisetupcommand.cpp | 13 ++- lib/cli/apisetupcommand.hpp | 2 + lib/cli/apisetuputility.cpp | 10 +-- lib/cli/apisetuputility.hpp | 4 +- lib/cli/featureutility.cpp | 14 ++-- lib/cli/featureutility.hpp | 4 +- lib/remote/apilistener.cpp | 49 +++++++++-- lib/remote/apilistener.hpp | 1 + 10 files changed, 179 insertions(+), 27 deletions(-) diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index 404cedf30..957cea61b 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -32,6 +32,8 @@ #include "base/context.hpp" #include "base/console.hpp" #include "base/process.hpp" +#include "base/json.hpp" +#include "base/tcpsocket.hpp" #include "config.h" #include #include @@ -46,6 +48,9 @@ # include # include # include + +# define popen _popen +# define pclose _pclose #endif /* _WIN32 */ using namespace icinga; @@ -215,8 +220,6 @@ static int Main() Application::DeclareConcurrency(std::thread::hardware_concurrency()); Application::DeclareMaxConcurrentChecks(Application::GetDefaultMaxConcurrentChecks()); - ScriptGlobal::Set("Environment", "production"); - ScriptGlobal::Set("AttachDebugger", false); ScriptGlobal::Set("PlatformKernel", Utility::GetPlatformKernel()); @@ -247,7 +250,8 @@ static int Main() ("include,I", po::value >(), "add include search directory") ("log-level,x", po::value(), "specify the log level for the console log.\n" "The valid value is either debug, notice, information (default), warning, or critical") - ("script-debugger,X", "whether to enable the script debugger"); + ("script-debugger,X", "whether to enable the script debugger") + ("env", po::value(), "the name of the environment, e.g. \"production\""); po::options_description hiddenDesc("Hidden options"); @@ -271,6 +275,99 @@ static int Main() return EXIT_FAILURE; } + if (vm.count("env") && vm["env"].as() != "") { + String env = vm["env"].as(); + ScriptGlobal::Set("Environment", env); + } else { +#ifndef _WIN32 + String cmd = "icinga-envs"; +#else /* _WIN32 */ + String cmd = "\"" + Utility::DirName(Application::GetExePath(argv[0])) + "\\icinga-envs.exe" + "\""; +#endif /* _WIN32 */ + cmd += " get-default"; +#ifndef _WIN32 + cmd += " 2>/dev/null"; +#else /* _WIN32 */ + cmd += " 2>NUL"; + cmd = "\"" + cmd + "\""; +#endif /* _WIN32 */ + + FILE *fp = popen(cmd.CStr(), "r"); + std::stringstream msgbuf; + while (!ferror(fp) && !feof(fp)) { + char buf[512]; + size_t num = fread(buf, 1, sizeof(buf), fp); + msgbuf << std::string(buf, buf + num); + } + pclose(fp); + + String env = msgbuf.str(); + ScriptGlobal::Set("Environment", env.Trim()); + } + + String env = ScriptGlobal::Get("Environment", NULL); +#ifndef _WIN32 + String cmd = "icinga-envs"; +#else /* _WIN32 */ + String cmd = "\"" + Utility::DirName(Application::GetExePath(argv[0])) + "\\icinga-envs.exe" + "\""; +#endif /* _WIN32 */ + cmd += " info --json " + Utility::EscapeShellArg(env); +#ifndef _WIN32 + cmd += " 2>/dev/null"; +#else /* _WIN32 */ + cmd += " 2>NUL"; + cmd = "\"" + cmd + "\""; +#endif /* _WIN32 */ + + FILE *fp = popen(cmd.CStr(), "r"); + std::stringstream msgbuf; + while (!ferror(fp) && !feof(fp)) { + char buf[512]; + size_t num = fread(buf, 1, sizeof(buf), fp); + msgbuf << std::string(buf, buf + num); + } + pclose(fp); + + Dictionary::Ptr envInfo; + try { + envInfo = JsonDecode(msgbuf.str()); + } catch (const std::exception&) {} + + if (envInfo) { + Dictionary::Ptr config = envInfo->Get("config"); + + if (!config) { + Log(LogCritical, "app") + << "Invalid JSON returned by icinga-envs: " << msgbuf.str(); + return EXIT_FAILURE; + } + + String configPath = config->Get("config_path"); + String dataPath = config->Get("data_path"); + + ScriptGlobal::Set("SysconfDir", configPath); + ScriptGlobal::Set("RunDir", dataPath + "/run"); + ScriptGlobal::Set("LocalStateDir", dataPath); + + TcpSocket::Ptr agentListener = new TcpSocket(); + agentListener->Bind("127.0.0.1", "0", AF_INET); + auto pieces = agentListener->GetClientAddress().Split(":"); + config->Set("port", Convert::ToLong(pieces[pieces.size() - 1])); + + Dictionary::Ptr envData = new Dictionary({ + { "listener", agentListener }, + { "config", config }, + { "meta_path", envInfo->Get("env_path") + "/config.json" } + }); + + // Make the agent listener available to the ApiListener later on + ScriptGlobal::Set("EnvironmentInfo", envData); + } else if (env != "") { + Log(LogCritical, "app") + << "No such environment exists: " << env; + return EXIT_FAILURE; + } + #ifdef _WIN32 char username[UNLEN + 1]; DWORD usernameLen = UNLEN + 1; diff --git a/icinga-installer/icinga2.wixpatch.cmake b/icinga-installer/icinga2.wixpatch.cmake index 0666261d2..ab400ed1e 100644 --- a/icinga-installer/icinga2.wixpatch.cmake +++ b/icinga-installer/icinga2.wixpatch.cmake @@ -10,9 +10,9 @@ - $CM_CP_sbin.icinga2_installer.exe>2 - $CM_CP_sbin.icinga2_installer.exe>2 - $CM_CP_sbin.icinga2_installer.exe=2 + $CM_CP_sbin.icinga2_installer.exe>2 AND NOT SUPPRESS_XTRA + $CM_CP_sbin.icinga2_installer.exe>2 AND NOT SUPPRESS_XTRA + $CM_CP_sbin.icinga2_installer.exe=2 AND NOT SUPPRESS_XTRA diff --git a/lib/cli/apisetupcommand.cpp b/lib/cli/apisetupcommand.cpp index bc832c525..f13b985a1 100644 --- a/lib/cli/apisetupcommand.cpp +++ b/lib/cli/apisetupcommand.cpp @@ -49,6 +49,14 @@ int ApiSetupCommand::GetMaxArguments() const return -1; } +void ApiSetupCommand::InitParameters(boost::program_options::options_description& visibleDesc, + boost::program_options::options_description& hiddenDesc) const +{ + visibleDesc.add_options() + ("quiet,q", "be less verbose") + ; +} + /** * The entry point for the "api setup" CLI command. * @@ -56,12 +64,15 @@ int ApiSetupCommand::GetMaxArguments() const */ int ApiSetupCommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const { + if (vm.count("quiet")) + Logger::SetConsoleLogSeverity(LogWarning); + String cn = VariableUtility::GetVariable("NodeName"); if (cn.IsEmpty()) cn = Utility::GetFQDN(); - if (!ApiSetupUtility::SetupMaster(cn, true)) + if (!ApiSetupUtility::SetupMaster(cn, true, vm.count("quiet"))) return 1; return 0; diff --git a/lib/cli/apisetupcommand.hpp b/lib/cli/apisetupcommand.hpp index f54da0685..fcdb56428 100644 --- a/lib/cli/apisetupcommand.hpp +++ b/lib/cli/apisetupcommand.hpp @@ -38,6 +38,8 @@ public: String GetDescription() const override; String GetShortDescription() const override; int GetMaxArguments() const override; + void InitParameters(boost::program_options::options_description& visibleDesc, + boost::program_options::options_description& hiddenDesc) const override; int Run(const boost::program_options::variables_map& vm, const std::vector& ap) const override; ImpersonationLevel GetImpersonationLevel() const override; }; diff --git a/lib/cli/apisetuputility.cpp b/lib/cli/apisetuputility.cpp index 070e78cff..d085fffc3 100644 --- a/lib/cli/apisetuputility.cpp +++ b/lib/cli/apisetuputility.cpp @@ -48,7 +48,7 @@ String ApiSetupUtility::GetApiUsersConfPath() return ApiSetupUtility::GetConfdPath() + "/api-users.conf"; } -bool ApiSetupUtility::SetupMaster(const String& cn, bool prompt_restart) +bool ApiSetupUtility::SetupMaster(const String& cn, bool prompt_restart, bool quiet) { if (!SetupMasterCertificates(cn)) return false; @@ -56,13 +56,13 @@ bool ApiSetupUtility::SetupMaster(const String& cn, bool prompt_restart) if (!SetupMasterApiUser()) return false; - if (!SetupMasterEnableApi()) + if (!SetupMasterEnableApi(quiet)) return false; if (!SetupMasterUpdateConstants(cn)) return false; - if (prompt_restart) { + if (prompt_restart && !quiet) { std::cout << "Done.\n\n"; std::cout << "Now restart your Icinga 2 daemon to finish the installation!\n\n"; } @@ -196,11 +196,11 @@ bool ApiSetupUtility::SetupMasterApiUser() return true; } -bool ApiSetupUtility::SetupMasterEnableApi() +bool ApiSetupUtility::SetupMasterEnableApi(bool quiet) { Log(LogInformation, "cli", "Enabling the 'api' feature."); - FeatureUtility::EnableFeatures({ "api" }); + FeatureUtility::EnableFeatures({ "api" }, quiet); return true; } diff --git a/lib/cli/apisetuputility.hpp b/lib/cli/apisetuputility.hpp index 9523ebffc..26b163d90 100644 --- a/lib/cli/apisetuputility.hpp +++ b/lib/cli/apisetuputility.hpp @@ -37,11 +37,11 @@ namespace icinga class ApiSetupUtility { public: - static bool SetupMaster(const String& cn, bool prompt_restart = false); + static bool SetupMaster(const String& cn, bool prompt_restart = false, bool quiet = false); static bool SetupMasterCertificates(const String& cn); static bool SetupMasterApiUser(); - static bool SetupMasterEnableApi(); + static bool SetupMasterEnableApi(bool quiet = false); static bool SetupMasterUpdateConstants(const String& cn); static String GetConfdPath(); diff --git a/lib/cli/featureutility.cpp b/lib/cli/featureutility.cpp index 9056b60fe..e06690f0d 100644 --- a/lib/cli/featureutility.cpp +++ b/lib/cli/featureutility.cpp @@ -56,7 +56,7 @@ std::vector FeatureUtility::GetFieldCompletionSuggestions(const String& return suggestions; } -int FeatureUtility::EnableFeatures(const std::vector& features) +int FeatureUtility::EnableFeatures(const std::vector& features, bool quiet) { String features_available_dir = GetFeaturesAvailablePath(); String features_enabled_dir = GetFeaturesEnabledPath(); @@ -93,8 +93,9 @@ int FeatureUtility::EnableFeatures(const std::vector& features) continue; } - std::cout << "Enabling feature " << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << feature - << ConsoleColorTag(Console_Normal) << ". Make sure to restart Icinga 2 for these changes to take effect.\n"; + if (!quiet) + std::cout << "Enabling feature " << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << feature + << ConsoleColorTag(Console_Normal) << ". Make sure to restart Icinga 2 for these changes to take effect.\n"; #ifndef _WIN32 String relativeSource = "../features-available/" + feature + ".conf"; @@ -131,7 +132,7 @@ int FeatureUtility::EnableFeatures(const std::vector& features) return 0; } -int FeatureUtility::DisableFeatures(const std::vector& features) +int FeatureUtility::DisableFeatures(const std::vector& features, bool quiet) { String features_enabled_dir = GetFeaturesEnabledPath(); @@ -160,8 +161,9 @@ int FeatureUtility::DisableFeatures(const std::vector& features) continue; } - std::cout << "Disabling feature " << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << feature - << ConsoleColorTag(Console_Normal) << ". Make sure to restart Icinga 2 for these changes to take effect.\n"; + if (!quiet) + std::cout << "Disabling feature " << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << feature + << ConsoleColorTag(Console_Normal) << ". Make sure to restart Icinga 2 for these changes to take effect.\n"; } if (!errors.empty()) { diff --git a/lib/cli/featureutility.hpp b/lib/cli/featureutility.hpp index 38f91cce0..23115ccca 100644 --- a/lib/cli/featureutility.hpp +++ b/lib/cli/featureutility.hpp @@ -40,8 +40,8 @@ public: static std::vector GetFieldCompletionSuggestions(const String& word, bool enable); - static int EnableFeatures(const std::vector& features); - static int DisableFeatures(const std::vector& features); + static int EnableFeatures(const std::vector& features, bool quiet = false); + static int DisableFeatures(const std::vector& features, bool quiet = false); static int ListFeatures(std::ostream& os = std::cout); static bool GetFeatures(std::vector& features, bool enable); diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index c1546710d..d1190a703 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -224,11 +224,23 @@ void ApiListener::Start(bool runtimeCreated) OpenLogFile(); } - /* create the primary JSON-RPC listener */ - if (!AddListener(GetBindHost(), GetBindPort())) { - Log(LogCritical, "ApiListener") - << "Cannot add listener on host '" << GetBindHost() << "' for port '" << GetBindPort() << "'."; - Application::Exit(EXIT_FAILURE); + Dictionary::Ptr envData = ScriptGlobal::Get("EnvironmentInfo", &Empty); + if (envData) { + ScriptGlobal::Set("EnvironmentInfo", Empty); + TcpSocket::Ptr listener = envData->Get("listener"); + if (!AddListener(listener)) { + Log(LogCritical, "ApiListener") + << "Failed to set up pre-configured agent listener."; + Application::Exit(EXIT_FAILURE); + } + Utility::SaveJsonFile(envData->Get("meta_path"), 0600, envData->Get("config")); + } else { + /* create the primary JSON-RPC listener */ + if (!AddListener(GetBindHost(), GetBindPort())) { + Log(LogCritical, "ApiListener") + << "Cannot add listener on host '" << GetBindHost() << "' for port '" << GetBindPort() << "'."; + Application::Exit(EXIT_FAILURE); + } } m_Timer = new Timer(); @@ -301,6 +313,33 @@ bool ApiListener::IsMaster() const return master == GetLocalEndpoint(); } +/** + * Creates a new JSON-RPC listener using the specified TCP socket object. + * + * @param listener The TCP socket to use. + */ +bool ApiListener::AddListener(const TcpSocket::Ptr& listener) +{ + ObjectLock olock(this); + + std::shared_ptr sslContext = m_SSLContext; + + if (!sslContext) { + Log(LogCritical, "ApiListener", "SSL context is required for AddListener()"); + return false; + } + + Log(LogInformation, "ApiListener") + << "Adding pre-configured listener on address " << listener->GetClientAddress(); + + std::thread thread(std::bind(&ApiListener::ListenerThreadProc, this, listener)); + thread.detach(); + + m_Servers.insert(listener); + + return true; +} + /** * Creates a new JSON-RPC listener on the specified port. * diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index b3894992a..41c4c3d23 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -138,6 +138,7 @@ private: void CleanupCertificateRequestsTimerHandler(); bool AddListener(const String& node, const String& service); + bool AddListener(const TcpSocket::Ptr& listener); void AddConnection(const Endpoint::Ptr& endpoint); void NewClientHandler(const Socket::Ptr& client, const String& hostname, ConnectionRole role);