diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 8faedbdef..8bc7e3185 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -102,13 +102,15 @@ static void Encode(yajl_gen handle, const Value& value) } } -String icinga::JsonEncode(const Value& value) +String icinga::JsonEncode(const Value& value, bool pretty_print) { #if YAJL_MAJOR < 2 - yajl_gen_config conf = { 0, "" }; + yajl_gen_config conf = { pretty_print, "" }; yajl_gen handle = yajl_gen_alloc(&conf, NULL); #else /* YAJL_MAJOR */ yajl_gen handle = yajl_gen_alloc(NULL); + if (pretty_print) + yajl_gen_config(handle, yajl_gen_beautify, 1); #endif /* YAJL_MAJOR */ Encode(handle, value); diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 2c8c78a4f..7a4a0ea3b 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -28,7 +28,7 @@ namespace icinga class String; class Value; -I2_BASE_API String JsonEncode(const Value& value); +I2_BASE_API String JsonEncode(const Value& value, bool pretty_print = false); I2_BASE_API Value JsonDecode(const String& data); } diff --git a/lib/cli/daemoncommand.cpp b/lib/cli/daemoncommand.cpp index 1dcd0fad1..6129c5cd2 100644 --- a/lib/cli/daemoncommand.cpp +++ b/lib/cli/daemoncommand.cpp @@ -350,7 +350,8 @@ int DaemonCommand::Run(const po::variables_map& vm, const std::vector #ifdef HAVE_LIBREADLINE @@ -60,6 +63,9 @@ ImpersonationLevel ReplCommand::GetImpersonationLevel(void) const void ReplCommand::InitParameters(boost::program_options::options_description& visibleDesc, boost::program_options::options_description& hiddenDesc) const { + visibleDesc.add_options() + ("connect,c", po::value(), "connect to an Icinga 2 instance") + ; } /** @@ -73,6 +79,13 @@ int ReplCommand::Run(const po::variables_map& vm, const std::vector std::map lines; int next_line = 1; + String addr, session; + + if (vm.count("connect")) { + addr = vm["connect"].as(); + session = Utility::NewUniqueID(); + } + std::cout << "Icinga (version: " << Application::GetVersion() << ")\n"; while (std::cin.good()) { @@ -115,46 +128,79 @@ int ReplCommand::Run(const po::variables_map& vm, const std::vector std::getline(std::cin, line); #endif /* HAVE_LIBREADLINE */ - Expression *expr; + if (addr.IsEmpty()) { + Expression *expr; - try { - ConfigCompilerContext::GetInstance()->Reset(); + try { + ConfigCompilerContext::GetInstance()->Reset(); - lines[fileName] = line; + lines[fileName] = line; - expr = ConfigCompiler::CompileText(fileName, line); + expr = ConfigCompiler::CompileText(fileName, line); - bool has_errors = false; + bool has_errors = false; - BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) { - if (message.Error) - has_errors = true; + BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) { + if (message.Error) + has_errors = true; - std::cout << (message.Error ? "Error" : "Warning") << ": " << message.Text << "\n"; + std::cout << (message.Error ? "Error" : "Warning") << ": " << message.Text << "\n"; + } + + if (expr && !has_errors) { + Value result = expr->Evaluate(frame); + std::cout << ConsoleColorTag(Console_ForegroundCyan); + if (!result.IsObject() || result.IsObjectType() || result.IsObjectType()) + std::cout << JsonEncode(result); + else + std::cout << result; + std::cout << ConsoleColorTag(Console_Normal) << "\n"; + } + } catch (const ScriptError& ex) { + DebugInfo di = ex.GetDebugInfo(); + + std::cout << di.Path << ": " << lines[di.Path] << "\n"; + std::cout << String(di.Path.GetLength() + 2, ' '); + std::cout << String(di.FirstColumn, ' ') << String(di.LastColumn - di.FirstColumn + 1, '^') << "\n"; + + std::cout << ex.what() << "\n"; + } catch (const std::exception& ex) { + std::cout << "Error: " << DiagnosticInformation(ex) << "\n"; } - if (expr && !has_errors) { - Value result = expr->Evaluate(frame); - std::cout << ConsoleColorTag(Console_ForegroundCyan); - if (!result.IsObject() || result.IsObjectType() || result.IsObjectType()) - std::cout << JsonEncode(result); - else - std::cout << result; - std::cout << ConsoleColorTag(Console_Normal) << "\n"; + delete expr; + } else { + Socket::Ptr socket; + + if (addr[0] == '/') { + UnixSocket::Ptr usocket = new UnixSocket(); + usocket->Connect(addr); + socket = usocket; + } else { + Log(LogCritical, "ReplCommand", "Sorry, TCP sockets aren't supported yet."); + return 1; } - } catch (const ScriptError& ex) { - DebugInfo di = ex.GetDebugInfo(); - std::cout << di.Path << ": " << lines[di.Path] << "\n"; - std::cout << String(di.Path.GetLength() + 2, ' '); - std::cout << String(di.FirstColumn, ' ') << String(di.LastColumn - di.FirstColumn + 1, '^') << "\n"; + String query = "SCRIPT " + session + "\n" + line + "\n\n"; - std::cout << ex.what() << "\n"; - } catch (const std::exception& ex) { - std::cout << "Error: " << DiagnosticInformation(ex) << "\n"; + NetworkStream::Ptr ns = new NetworkStream(socket); + ns->Write(query.CStr(), query.GetLength()); + + String result; + char buf[1024]; + + while (!ns->IsEof()) { + size_t rc = ns->Read(buf, sizeof(buf)); + result += String(buf, buf + rc); + } + + if (result.GetLength() < 16) { + Log(LogCritical, "ReplCommand", "Received invalid response from Livestatus."); + continue; + } + + std::cout << result.SubStr(16) << "\n"; } - - delete expr; } return 0; diff --git a/lib/livestatus/livestatusquery.cpp b/lib/livestatus/livestatusquery.cpp index 32fd88f3c..fbab49d4b 100644 --- a/lib/livestatus/livestatusquery.cpp +++ b/lib/livestatus/livestatusquery.cpp @@ -31,6 +31,7 @@ #include "livestatus/orfilter.hpp" #include "livestatus/andfilter.hpp" #include "icinga/externalcommandprocessor.hpp" +#include "config/configcompiler.hpp" #include "base/debug.hpp" #include "base/convert.hpp" #include "base/objectlock.hpp" @@ -38,15 +39,49 @@ #include "base/exception.hpp" #include "base/utility.hpp" #include "base/json.hpp" +#include "base/serializer.hpp" +#include "base/timer.hpp" +#include "base/initialize.hpp" #include #include #include #include +#include using namespace icinga; static int l_ExternalCommands = 0; static boost::mutex l_QueryMutex; +static std::map l_LivestatusScriptFrames; +static Timer::Ptr l_FrameCleanupTimer; +static boost::mutex l_LivestatusScriptMutex; + +static void ScriptFrameCleanupHandler(void) +{ + boost::mutex::scoped_lock lock(l_LivestatusScriptMutex); + + std::vector cleanup_keys; + + typedef std::pair KVPair; + + BOOST_FOREACH(const KVPair& kv, l_LivestatusScriptFrames) { + if (kv.second.Seen < Utility::GetTime() - 60) + cleanup_keys.push_back(kv.first); + } + + BOOST_FOREACH(const String& key, cleanup_keys) + l_LivestatusScriptFrames.erase(key); +} + +static void InitScriptFrameCleanup(void) +{ + l_FrameCleanupTimer = new Timer(); + l_FrameCleanupTimer->OnTimerExpired.connect(boost::bind(ScriptFrameCleanupHandler)); + l_FrameCleanupTimer->SetInterval(30); + l_FrameCleanupTimer->Start(); +} + +INITIALIZE_ONCE(InitScriptFrameCleanup); LivestatusQuery::LivestatusQuery(const std::vector& lines, const String& compat_log_path) : m_KeepAlive(false), m_OutputFormat("csv"), m_ColumnHeaders(true), @@ -88,6 +123,16 @@ LivestatusQuery::LivestatusQuery(const std::vector& lines, const String& if (m_Verb == "COMMAND") { m_KeepAlive = true; m_Command = target; + } else if (m_Verb == "SCRIPT") { + m_Session = target; + + for (unsigned int i = 1; i < lines.size(); i++) { + if (m_Command != "") + m_Command += "\n"; + m_Command += lines[i]; + } + + return; } else if (m_Verb == "GET") { m_Table = target; } else { @@ -549,6 +594,52 @@ void LivestatusQuery::ExecuteCommandHelper(const Stream::Ptr& stream) SendResponse(stream, LivestatusErrorOK, ""); } +void LivestatusQuery::ExecuteScriptHelper(const Stream::Ptr& stream) +{ + Log(LogInformation, "LivestatusQuery") + << "Executing expression: " << m_Command; + + m_ResponseHeader = "fixed16"; + + LivestatusScriptFrame& lsf = l_LivestatusScriptFrames[m_Session]; + lsf.Seen = Utility::GetTime(); + + if (!lsf.Locals) + lsf.Locals = new Dictionary(); + + String fileName = "<" + Convert::ToString(lsf.NextLine) + ">"; + lsf.NextLine++; + + lsf.Lines[fileName] = m_Command; + + Expression *expr = ConfigCompiler::CompileText(fileName, m_Command); + Value result; + try { + ScriptFrame frame; + frame.Locals = lsf.Locals; + result = expr->Evaluate(frame); + } catch (const ScriptError& ex) { + delete expr; + + DebugInfo di = ex.GetDebugInfo(); + + std::ostringstream msgbuf; + + msgbuf << di.Path << ": " << lsf.Lines[di.Path] << "\n" + << String(di.Path.GetLength() + 2, ' ') + << String(di.FirstColumn, ' ') << String(di.LastColumn - di.FirstColumn + 1, '^') << "\n" + << ex.what() << "\n"; + + SendResponse(stream, LivestatusErrorQuery, msgbuf.str()); + return; + } catch (...) { + delete expr; + throw; + } + delete expr; + SendResponse(stream, LivestatusErrorOK, JsonEncode(Serialize(result, FAState | FAConfig), true)); +} + void LivestatusQuery::ExecuteErrorHelper(const Stream::Ptr& stream) { Log(LogDebug, "LivestatusQuery") @@ -596,6 +687,8 @@ bool LivestatusQuery::Execute(const Stream::Ptr& stream) ExecuteGetHelper(stream); else if (m_Verb == "COMMAND") ExecuteCommandHelper(stream); + else if (m_Verb == "SCRIPT") + ExecuteScriptHelper(stream); else if (m_Verb == "ERROR") ExecuteErrorHelper(stream); else diff --git a/lib/livestatus/livestatusquery.hpp b/lib/livestatus/livestatusquery.hpp index 65af9fc38..c69119c72 100644 --- a/lib/livestatus/livestatusquery.hpp +++ b/lib/livestatus/livestatusquery.hpp @@ -25,6 +25,7 @@ #include "base/object.hpp" #include "base/array.hpp" #include "base/stream.hpp" +#include "base/scriptframe.hpp" #include using namespace icinga; @@ -39,6 +40,18 @@ enum LivestatusError LivestatusErrorQuery = 452 }; +struct LivestatusScriptFrame +{ + double Seen; + int NextLine; + std::map Lines; + Dictionary::Ptr Locals; + + LivestatusScriptFrame(void) + : NextLine(1) + { } +}; + /** * @ingroup livestatus */ @@ -71,8 +84,9 @@ private: String m_ResponseHeader; - /* Parameters for COMMAND queries. */ + /* Parameters for COMMAND/SCRIPT queries. */ String m_Command; + String m_Session; /* Parameters for invalid queries. */ int m_ErrorCode; @@ -89,6 +103,7 @@ private: void ExecuteGetHelper(const Stream::Ptr& stream); void ExecuteCommandHelper(const Stream::Ptr& stream); + void ExecuteScriptHelper(const Stream::Ptr& stream); void ExecuteErrorHelper(const Stream::Ptr& stream); void SendResponse(const Stream::Ptr& stream, int code, const String& data);