add_executable(icinga-studio MACOSX_BUNDLE WIN32 icinga-studio.cpp
forms.cpp aboutform.cpp connectform.cpp mainform.cpp
- icinga.icns apiclient.cpp ${WindowsSources})
+ icinga.icns ${WindowsSources})
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries(icinga-studio ${Boost_LIBRARIES} ${wxWidgets_LIBRARIES} base remote)
m_PropertyGrid->Clear();
if (eptr) {
-
try {
boost::rethrow_exception(eptr);
} catch (const std::exception& ex) {
#ifndef MAINFORM_H
#define MAINFORM_H
-#include "icinga-studio/apiclient.hpp"
+#include "remote/apiclient.hpp"
#include "remote/url.hpp"
#include "base/exception.hpp"
#include "icinga-studio/forms.h"
for (int i = 0; i < type->GetFieldCount(); i++) {
Field field = type->GetFieldInfo(i);
- if ((field.Attributes & attributeTypes) == 0)
+ if (attributeTypes != 0 && (field.Attributes & attributeTypes) == 0)
continue;
fields->Set(field.Name, Serialize(input->GetField(i), attributeTypes));
#include "cli/consolecommand.hpp"
#include "config/configcompiler.hpp"
+#include "remote/apiclient.hpp"
+#include "remote/consolehandler.hpp"
+#include "remote/url.hpp"
#include "base/configwriter.hpp"
+#include "base/serializer.hpp"
#include "base/json.hpp"
#include "base/console.hpp"
#include "base/application.hpp"
namespace po = boost::program_options;
static ScriptFrame *l_ScriptFrame;
+static ApiClient::Ptr l_ApiClient;
+static String l_Session;
REGISTER_CLICOMMAND("console", ConsoleCommand);
{
visibleDesc.add_options()
("connect,c", po::value<std::string>(), "connect to an Icinga 2 instance")
+ ("eval,e", po::value<std::string>(), "evaluate expression and terminate")
("sandbox", "enable sandbox mode")
;
}
#ifdef HAVE_EDITLINE
-static void AddSuggestion(std::vector<String>& matches, const String& word, const String& suggestion)
-{
- if (suggestion.Find(word) != 0)
- return;
-
- matches.push_back(suggestion);
-}
-
-static char *ConsoleCompleteHelper(const char *word, int state)
+char *ConsoleCommand::ConsoleCompleteHelper(const char *word, int state)
{
static std::vector<String> matches;
- String aword = word;
if (state == 0) {
- matches.clear();
-
- BOOST_FOREACH(const String& keyword, ConfigWriter::GetKeywords()) {
- AddSuggestion(matches, word, keyword);
- }
-
- {
- ObjectLock olock(l_ScriptFrame->Locals);
- BOOST_FOREACH(const Dictionary::Pair& kv, l_ScriptFrame->Locals) {
- AddSuggestion(matches, word, kv.first);
- }
- }
-
- {
- ObjectLock olock(ScriptGlobal::GetGlobals());
- BOOST_FOREACH(const Dictionary::Pair& kv, ScriptGlobal::GetGlobals()) {
- AddSuggestion(matches, word, kv.first);
+ if (!l_ApiClient)
+ matches = ConsoleHandler::GetAutocompletionSuggestions(word, *l_ScriptFrame);
+ else {
+ boost::mutex mutex;
+ boost::condition_variable cv;
+ bool ready = false;
+ Array::Ptr suggestions;
+
+ l_ApiClient->AutocompleteScript(l_Session, word, l_ScriptFrame->Sandboxed,
+ boost::bind(&ConsoleCommand::AutocompleteScriptCompletionHandler,
+ boost::ref(mutex), boost::ref(cv), boost::ref(ready),
+ _1, _2,
+ boost::ref(suggestions)));
+
+ {
+ boost::mutex::scoped_lock lock(mutex);
+ while (!ready)
+ cv.wait(lock);
}
- }
-
- String::SizeType cperiod = aword.RFind(".");
-
- if (cperiod != -1) {
- String pword = aword.SubStr(0, cperiod);
-
- Value value;
-
- try {
- Expression *expr = ConfigCompiler::CompileText("temp", pword);
-
- if (expr)
- value = expr->Evaluate(*l_ScriptFrame);
-
- if (value.IsObjectType<Dictionary>()) {
- Dictionary::Ptr dict = value;
-
- ObjectLock olock(dict);
- BOOST_FOREACH(const Dictionary::Pair& kv, dict) {
- AddSuggestion(matches, word, pword + "." + kv.first);
- }
- }
-
- Type::Ptr type = value.GetReflectionType();
- for (int i = 0; i < type->GetFieldCount(); i++) {
- Field field = type->GetFieldInfo(i);
-
- AddSuggestion(matches, word, pword + "." + field.Name);
- }
+ matches.clear();
- while (type) {
- Object::Ptr prototype = type->GetPrototype();
- Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(prototype);
-
- if (dict) {
- ObjectLock olock(dict);
- BOOST_FOREACH(const Dictionary::Pair& kv, dict) {
- AddSuggestion(matches, word, pword + "." + kv.first);
- }
- }
-
- type = type->GetBaseType();
- }
- } catch (...) { /* Ignore the exception */ }
+ ObjectLock olock(suggestions);
+ std::copy(suggestions->Begin(), suggestions->End(), std::back_inserter(matches));
}
}
int next_line = 1;
#ifdef HAVE_EDITLINE
- rl_completion_entry_function = ConsoleCompleteHelper;
+ rl_completion_entry_function = ConsoleCommand::ConsoleCompleteHelper;
rl_completion_append_character = '\0';
#endif /* HAVE_EDITLINE */
- String addr, session;
+ String addr;
ScriptFrame scriptFrame;
l_ScriptFrame = &scriptFrame;
+ l_Session = Utility::NewUniqueID();
+
+ if (vm.count("sandbox"))
+ scriptFrame.Sandboxed = true;
+
+ if (!vm.count("eval"))
+ std::cout << "Icinga 2 (version: " << Application::GetAppVersion() << ")\n";
+
+ const char *addrEnv = getenv("ICINGA2_API_URL");
+ if (addrEnv)
+ addr = addrEnv;
if (vm.count("connect")) {
addr = vm["connect"].as<std::string>();
- session = Utility::NewUniqueID();
}
- if (vm.count("sandbox")) {
- if (vm.count("connect")) {
- Log(LogCritical, "ConsoleCommand", "Sandbox mode cannot be used together with --connect.");
+ if (!addr.IsEmpty()) {
+ Url::Ptr url;
+
+ try {
+ url = new Url(addr);
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "ConsoleCommand", ex.what());
return EXIT_FAILURE;
}
- scriptFrame.Sandboxed = true;
- }
+ const char *usernameEnv = getenv("ICINGA2_API_USERNAME");
+ const char *passwordEnv = getenv("ICINGA2_API_PASSWORD");
- std::cout << "Icinga (version: " << Application::GetAppVersion() << ")\n";
+ if (usernameEnv)
+ url->SetUsername(usernameEnv);
+ if (passwordEnv)
+ url->SetPassword(passwordEnv);
+
+ if (url->GetPort().IsEmpty())
+ url->SetPort("5665");
+
+ l_ApiClient = new ApiClient(url->GetHost(), url->GetPort(), url->GetUsername(), url->GetPassword());
+ }
while (std::cin.good()) {
String fileName = "<" + Convert::ToString(next_line) + ">";
std::string command;
incomplete:
+ std::string line;
+
+ if (!vm.count("eval")) {
#ifdef HAVE_EDITLINE
- std::ostringstream promptbuf;
- std::ostream& os = promptbuf;
+ std::ostringstream promptbuf;
+ std::ostream& os = promptbuf;
#else /* HAVE_EDITLINE */
- std::ostream& os = std::cout;
+ std::ostream& os = std::cout;
#endif /* HAVE_EDITLINE */
- os << fileName;
+ os << fileName;
- if (!continuation)
- os << " => ";
- else
- os << " .. ";
+ if (!continuation)
+ os << " => ";
+ else
+ os << " .. ";
#ifdef HAVE_EDITLINE
- String prompt = promptbuf.str();
+ String prompt = promptbuf.str();
- char *cline;
- cline = readline(prompt.CStr());
+ char *cline;
+ cline = readline(prompt.CStr());
- if (!cline)
- break;
+ if (!cline)
+ break;
- add_history(cline);
+ add_history(cline);
- std::string line = cline;
+ line = cline;
- free(cline);
+ free(cline);
#else /* HAVE_EDITLINE */
- std::string line;
- std::getline(std::cin, line);
+ std::getline(std::cin, line);
#endif /* HAVE_EDITLINE */
+ } else
+ line = vm["eval"].as<std::string>();
if (!command.empty())
command += "\n";
command += line;
- if (addr.IsEmpty()) {
- Expression *expr = NULL;
+ Expression *expr = NULL;
- try {
- lines[fileName] = command;
+ try {
+ lines[fileName] = command;
- expr = ConfigCompiler::CompileText(fileName, command);
+ Value result;
- if (expr) {
- Value result = expr->Evaluate(scriptFrame);
- std::cout << ConsoleColorTag(Console_ForegroundCyan);
- if (!result.IsObject() || result.IsObjectType<Array>() || result.IsObjectType<Dictionary>())
- std::cout << JsonEncode(result);
- else
- std::cout << result;
- std::cout << ConsoleColorTag(Console_Normal) << "\n";
- }
- } catch (const ScriptError& ex) {
- if (ex.IsIncompleteExpression()) {
- continuation = true;
- goto incomplete;
+ if (!l_ApiClient) {
+ expr = ConfigCompiler::CompileText(fileName, command);
+ result = Serialize(expr->Evaluate(scriptFrame), 0);
+ } else {
+ boost::mutex mutex;
+ boost::condition_variable cv;
+ bool ready = false;
+ boost::exception_ptr eptr;
+
+ l_ApiClient->ExecuteScript(l_Session, command, scriptFrame.Sandboxed,
+ boost::bind(&ConsoleCommand::ExecuteScriptCompletionHandler,
+ boost::ref(mutex), boost::ref(cv), boost::ref(ready),
+ _1, _2,
+ boost::ref(result), boost::ref(eptr)));
+
+ {
+ boost::mutex::scoped_lock lock(mutex);
+ while (!ready)
+ cv.wait(lock);
}
- DebugInfo di = ex.GetDebugInfo();
+ if (eptr)
+ boost::rethrow_exception(eptr);
+ }
- if (lines.find(di.Path) != lines.end()) {
- String text = lines[di.Path];
+ if (!vm.count("eval")) {
+ std::cout << ConsoleColorTag(Console_ForegroundCyan);
+ ConfigWriter::EmitValue(std::cout, 1, result);
+ std::cout << ConsoleColorTag(Console_Normal) << "\n";
+ } else {
+ std::cout << JsonEncode(result) << "\n";
+ break;
+ }
+ } catch (const ScriptError& ex) {
+ if (ex.IsIncompleteExpression()) {
+ continuation = true;
+ goto incomplete;
+ }
- std::vector<String> ulines;
- boost::algorithm::split(ulines, text, boost::is_any_of("\n"));
+ DebugInfo di = ex.GetDebugInfo();
- for (int i = 1; i <= ulines.size(); i++) {
- int start, len;
+ if (lines.find(di.Path) != lines.end()) {
+ String text = lines[di.Path];
- if (i == di.FirstLine)
- start = di.FirstColumn;
- else
- start = 0;
+ std::vector<String> ulines;
+ boost::algorithm::split(ulines, text, boost::is_any_of("\n"));
- if (i == di.LastLine)
- len = di.LastColumn - di.FirstColumn + 1;
- else
- len = ulines[i - 1].GetLength();
+ for (int i = 1; i <= ulines.size(); i++) {
+ int start, len;
- int offset;
+ if (i == di.FirstLine)
+ start = di.FirstColumn;
+ else
+ start = 0;
- if (di.Path != fileName) {
- std::cout << di.Path << ": " << ulines[i - 1] << "\n";
- offset = 2;
- } else
- offset = 4;
+ if (i == di.LastLine)
+ len = di.LastColumn - di.FirstColumn + 1;
+ else
+ len = ulines[i - 1].GetLength();
+
+ int offset;
- if (i >= di.FirstLine && i <= di.LastLine) {
- std::cout << String(di.Path.GetLength() + offset, ' ');
- std::cout << String(start, ' ') << String(len, '^') << "\n";
- }
+ if (di.Path != fileName) {
+ std::cout << di.Path << ": " << ulines[i - 1] << "\n";
+ offset = 2;
+ } else
+ offset = 4;
+
+ if (i >= di.FirstLine && i <= di.LastLine) {
+ std::cout << String(di.Path.GetLength() + offset, ' ');
+ std::cout << String(start, ' ') << String(len, '^') << "\n";
}
- } else {
- ShowCodeFragment(std::cout, di);
}
-
- std::cout << ex.what() << "\n";
- } catch (const std::exception& ex) {
- std::cout << "Error: " << DiagnosticInformation(ex) << "\n";
+ } else {
+ ShowCodeFragment(std::cout, di);
}
- delete expr;
- } else {
- Socket::Ptr socket;
+ std::cout << ex.what() << "\n";
-#ifndef _WIN32
- if (addr.FindFirstOf("/") != String::NPos) {
- UnixSocket::Ptr usocket = new UnixSocket();
- usocket->Connect(addr);
- socket = usocket;
- } else {
-#endif /* _WIN32 */
- Log(LogCritical, "ConsoleCommand", "Sorry, TCP sockets aren't supported yet.");
- return 1;
-#ifndef _WIN32
- }
-#endif /* _WIN32 */
+ if (vm.count("eval"))
+ return EXIT_FAILURE;
+ } catch (const std::exception& ex) {
+ std::cout << "Error: " << DiagnosticInformation(ex) << "\n";
- String query = "SCRIPT " + session + "\n" + line + "\n\n";
+ if (vm.count("eval"))
+ return EXIT_FAILURE;
+ }
- NetworkStream::Ptr ns = new NetworkStream(socket);
- ns->Write(query.CStr(), query.GetLength());
+ delete expr;
+ }
- String result;
- char buf[1024];
+ return EXIT_SUCCESS;
+}
- while (!ns->IsEof()) {
- size_t rc = ns->Read(buf, sizeof(buf), true);
- result += String(buf, buf + rc);
- }
+void ConsoleCommand::ExecuteScriptCompletionHandler(boost::mutex& mutex, boost::condition_variable& cv,
+ bool& ready, boost::exception_ptr eptr, const Value& result, Value& resultOut, boost::exception_ptr& eptrOut)
+{
+ if (eptr) {
+ try {
+ boost::rethrow_exception(eptr);
+ } catch (const ScriptError& ex) {
+ eptrOut = boost::current_exception();
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "ConsoleCommand")
+ << "HTTP query failed: " << ex.what();
+ Application::Exit(EXIT_FAILURE);
+ }
+ }
- if (result.GetLength() < 16) {
- Log(LogCritical, "ConsoleCommand", "Received invalid response from Livestatus.");
- continue;
- }
+ resultOut = result;
+
+ {
+ boost::mutex::scoped_lock lock(mutex);
+ ready = true;
+ cv.notify_all();
+ }
+}
- std::cout << result.SubStr(16) << "\n";
+void ConsoleCommand::AutocompleteScriptCompletionHandler(boost::mutex& mutex, boost::condition_variable& cv,
+ bool& ready, boost::exception_ptr eptr, const Array::Ptr& result, Array::Ptr& resultOut)
+{
+ if (eptr) {
+ try {
+ boost::rethrow_exception(eptr);
+ } catch (const std::exception& ex) {
+ Log(LogCritical, "ConsoleCommand")
+ << "HTTP query failed: " << ex.what();
+ Application::Exit(EXIT_FAILURE);
}
}
- return 0;
+ resultOut = result;
+
+ {
+ boost::mutex::scoped_lock lock(mutex);
+ ready = true;
+ cv.notify_all();
+ }
}
#define CONSOLECOMMAND_H
#include "cli/clicommand.hpp"
+#include "base/exception.hpp"
namespace icinga
{
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const override;
virtual int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
+
+private:
+ mutable boost::mutex m_Mutex;
+ mutable boost::condition_variable m_CV;
+ mutable bool m_CommandReady;
+
+ static void ExecuteScriptCompletionHandler(boost::mutex& mutex, boost::condition_variable& cv,
+ bool& ready, boost::exception_ptr eptr, const Value& result, Value& resultOut,
+ boost::exception_ptr& eptrOut);
+ static void AutocompleteScriptCompletionHandler(boost::mutex& mutex, boost::condition_variable& cv,
+ bool& ready, boost::exception_ptr eptr, const Array::Ptr& result, Array::Ptr& resultOut);
+
+#ifdef HAVE_EDITLINE
+ static char *ConsoleCompleteHelper(const char *word, int state);
+#endif /* HAVE_EDITLINE */
+
};
}
#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"
static int l_ExternalCommands = 0;
static boost::mutex l_QueryMutex;
-static std::map<String, LivestatusScriptFrame> 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<String> cleanup_keys;
-
- typedef std::pair<String, LivestatusScriptFrame> KVPair;
-
- BOOST_FOREACH(const KVPair& kv, l_LivestatusScriptFrames) {
- if (kv.second.Seen < Utility::GetTime() - 1800)
- 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<String>& lines, const String& compat_log_path)
: m_KeepAlive(false), m_OutputFormat("csv"), m_ColumnHeaders(true), m_Limit(-1), m_ErrorCode(0),
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 {
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 = NULL;
- Value result;
- try {
- expr = ConfigCompiler::CompileText(fileName, m_Command);
- ScriptFrame frame;
- frame.Locals = lsf.Locals;
- frame.Self = 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, FAEphemeral | FAState | FAConfig), true));
-}
-
void LivestatusQuery::ExecuteErrorHelper(const Stream::Ptr& stream)
{
Log(LogDebug, "LivestatusQuery")
ExecuteGetHelper(stream);
else if (m_Verb == "COMMAND")
ExecuteCommandHelper(stream);
- else if (m_Verb == "SCRIPT")
- ExecuteScriptHelper(stream);
else if (m_Verb == "ERROR")
ExecuteErrorHelper(stream);
else
LivestatusErrorQuery = 452
};
-struct LivestatusScriptFrame
-{
- double Seen;
- int NextLine;
- std::map<String, String> Lines;
- Dictionary::Ptr Locals;
-
- LivestatusScriptFrame(void)
- : Seen(0), NextLine(1)
- { }
-};
-
/**
* @ingroup livestatus
*/
/* Parameters for invalid queries. */
int m_ErrorCode;
String m_ErrorMessage;
-
+
unsigned long m_LogTimeFrom;
unsigned long m_LogTimeUntil;
String m_CompatLogPath;
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);
void PrintFixed16(const Stream::Ptr& stream, int code, const String& data);
-
+
static Filter::Ptr ParseFilter(const String& params, unsigned long& from, unsigned long& until);
};
mkclass_target(zone.ti zone.tcpp zone.thpp)
set(remote_SOURCES
- actionshandler.cpp apiaction.cpp
+ actionshandler.cpp apiaction.cpp apiclient.cpp
apifunction.cpp apilistener.cpp apilistener.thpp apilistener-configsync.cpp
apilistener-filesync.cpp apiuser.cpp apiuser.thpp authority.cpp base64.cpp
- configfileshandler.cpp configpackageshandler.cpp configpackageutility.cpp configobjectutility.cpp
+ consolehandler.cpp configfileshandler.cpp configpackageshandler.cpp configpackageutility.cpp configobjectutility.cpp
configstageshandler.cpp createobjecthandler.cpp deleteobjecthandler.cpp
endpoint.cpp endpoint.thpp eventshandler.cpp eventqueue.cpp filterutility.cpp
httpchunkedencoding.cpp httpclientconnection.cpp httpserverconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
-#include "icinga-studio/apiclient.hpp"
+#include "remote/apiclient.hpp"
#include "remote/base64.hpp"
#include "base/json.hpp"
#include "base/logger.hpp"
path.push_back("objects");
path.push_back(pluralType);
url->SetPath(path);
- String qp;
std::map<String, std::vector<String> > params;
callback(boost::current_exception(), std::vector<ApiObject::Ptr>());
}
}
+
+void ApiClient::ExecuteScript(const String& session, const String& command, bool sandboxed,
+ const ExecuteScriptCompletionCallback& callback) const
+{
+ Url::Ptr url = new Url();
+ url->SetScheme("https");
+ url->SetHost(m_Connection->GetHost());
+ url->SetPort(m_Connection->GetPort());
+
+ std::vector<String> path;
+ path.push_back("v1");
+ path.push_back("console");
+ path.push_back("execute-script");
+ url->SetPath(path);
+
+ std::map<String, std::vector<String> > params;
+ params["session"].push_back(session);
+ params["command"].push_back(command);
+ params["sandboxed"].push_back(sandboxed ? "1" : "0");
+ url->SetQuery(params);
+
+ try {
+ boost::shared_ptr<HttpRequest> req = m_Connection->NewRequest();
+ req->RequestMethod = "POST";
+ req->RequestUrl = url;
+ req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password));
+ m_Connection->SubmitRequest(req, boost::bind(ExecuteScriptHttpCompletionCallback, _1, _2, callback));
+ } catch (const std::exception& ex) {
+ callback(boost::current_exception(), Empty);
+ }
+}
+
+void ApiClient::ExecuteScriptHttpCompletionCallback(HttpRequest& request,
+ HttpResponse& response, const ExecuteScriptCompletionCallback& callback)
+{
+ Dictionary::Ptr result;
+
+ String body;
+ char buffer[1024];
+ size_t count;
+
+ while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0)
+ body += String(buffer, buffer + count);
+
+ try {
+ if (response.StatusCode < 200 || response.StatusCode > 299) {
+ std::string message = "HTTP request failed; Code: " + Convert::ToString(response.StatusCode) + "; Body: " + body;
+
+ BOOST_THROW_EXCEPTION(ScriptError(message));
+ }
+
+ result = JsonDecode(body);
+
+ Array::Ptr results = result->Get("results");
+ Value result;
+ bool incompleteExpression = false;
+ String errorMessage = "Unexpected result from API.";
+
+ if (results && results->GetLength() > 0) {
+ Dictionary::Ptr resultInfo = results->Get(0);
+ errorMessage = resultInfo->Get("status");
+
+ if (resultInfo->Get("code") >= 200 && resultInfo->Get("code") <= 299) {
+ result = resultInfo->Get("result");
+ } else {
+ DebugInfo di;
+ Dictionary::Ptr debugInfo = resultInfo->Get("debug_info");
+ if (debugInfo) {
+ di.Path = debugInfo->Get("path");
+ di.FirstLine = debugInfo->Get("first_line");
+ di.FirstColumn = debugInfo->Get("first_column");
+ di.LastLine = debugInfo->Get("last_line");
+ di.LastColumn = debugInfo->Get("last_column");
+ }
+ bool incompleteExpression = resultInfo->Get("incomplete_expression");
+ BOOST_THROW_EXCEPTION(ScriptError(errorMessage, di, incompleteExpression));
+ }
+ }
+
+ callback(boost::exception_ptr(), result);
+ } catch (const std::exception& ex) {
+ callback(boost::current_exception(), Empty);
+ }
+}
+
+void ApiClient::AutocompleteScript(const String& session, const String& command, bool sandboxed,
+ const AutocompleteScriptCompletionCallback& callback) const
+{
+ Url::Ptr url = new Url();
+ url->SetScheme("https");
+ url->SetHost(m_Connection->GetHost());
+ url->SetPort(m_Connection->GetPort());
+
+ std::vector<String> path;
+ path.push_back("v1");
+ path.push_back("console");
+ path.push_back("auto-complete-script");
+ url->SetPath(path);
+
+ std::map<String, std::vector<String> > params;
+ params["session"].push_back(session);
+ params["command"].push_back(command);
+ params["sandboxed"].push_back(sandboxed ? "1" : "0");
+ url->SetQuery(params);
+
+ try {
+ boost::shared_ptr<HttpRequest> req = m_Connection->NewRequest();
+ req->RequestMethod = "POST";
+ req->RequestUrl = url;
+ req->AddHeader("Authorization", "Basic " + Base64::Encode(m_User + ":" + m_Password));
+ m_Connection->SubmitRequest(req, boost::bind(AutocompleteScriptHttpCompletionCallback, _1, _2, callback));
+ } catch (const std::exception& ex) {
+ callback(boost::current_exception(), Array::Ptr());
+ }
+}
+
+void ApiClient::AutocompleteScriptHttpCompletionCallback(HttpRequest& request,
+ HttpResponse& response, const AutocompleteScriptCompletionCallback& callback)
+{
+ Dictionary::Ptr result;
+
+ String body;
+ char buffer[1024];
+ size_t count;
+
+ while ((count = response.ReadBody(buffer, sizeof(buffer))) > 0)
+ body += String(buffer, buffer + count);
+
+ try {
+ if (response.StatusCode < 200 || response.StatusCode > 299) {
+ std::string message = "HTTP request failed; Code: " + Convert::ToString(response.StatusCode) + "; Body: " + body;
+
+ BOOST_THROW_EXCEPTION(ScriptError(message));
+ }
+
+ result = JsonDecode(body);
+
+ Array::Ptr results = result->Get("results");
+ Array::Ptr suggestions;
+ String errorMessage = "Unexpected result from API.";
+
+ if (results && results->GetLength() > 0) {
+ Dictionary::Ptr resultInfo = results->Get(0);
+ errorMessage = resultInfo->Get("status");
+
+ if (resultInfo->Get("code") >= 200 && resultInfo->Get("code") <= 299)
+ suggestions = resultInfo->Get("suggestions");
+ else
+ BOOST_THROW_EXCEPTION(ScriptError(errorMessage));
+ }
+
+ callback(boost::exception_ptr(), suggestions);
+ } catch (const std::exception& ex) {
+ callback(boost::current_exception(), Array::Ptr());
+ }
+}
const std::vector<String>& names = std::vector<String>(),
const std::vector<String>& attrs = std::vector<String>()) const;
+ typedef boost::function<void(boost::exception_ptr, const Value&)> ExecuteScriptCompletionCallback;
+ void ExecuteScript(const String& session, const String& command, bool sandboxed,
+ const ExecuteScriptCompletionCallback& callback) const;
+
+ typedef boost::function<void(boost::exception_ptr, const Array::Ptr&)> AutocompleteScriptCompletionCallback;
+ void AutocompleteScript(const String& session, const String& command, bool sandboxed,
+ const AutocompleteScriptCompletionCallback& callback) const;
+
private:
HttpClientConnection::Ptr m_Connection;
String m_User;
HttpResponse& response, const TypesCompletionCallback& callback);
static void ObjectsHttpCompletionCallback(HttpRequest& request,
HttpResponse& response, const ObjectsCompletionCallback& callback);
+ static void ExecuteScriptHttpCompletionCallback(HttpRequest& request,
+ HttpResponse& response, const ExecuteScriptCompletionCallback& callback);
+ static void AutocompleteScriptHttpCompletionCallback(HttpRequest& request,
+ HttpResponse& response, const AutocompleteScriptCompletionCallback& callback);
};
}
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License *
+ * as published by the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#include "remote/consolehandler.hpp"
+#include "remote/httputility.hpp"
+#include "remote/filterutility.hpp"
+#include "config/configcompiler.hpp"
+#include "base/configtype.hpp"
+#include "base/configwriter.hpp"
+#include "base/scriptglobal.hpp"
+#include "base/logger.hpp"
+#include "base/serializer.hpp"
+#include "base/timer.hpp"
+#include "base/initialize.hpp"
+#include <boost/algorithm/string.hpp>
+#include <set>
+
+using namespace icinga;
+
+REGISTER_URLHANDLER("/v1/console", ConsoleHandler);
+
+static int l_ExternalCommands = 0;
+static boost::mutex l_QueryMutex;
+static std::map<String, ApiScriptFrame> l_ApiScriptFrames;
+static Timer::Ptr l_FrameCleanupTimer;
+static boost::mutex l_ApiScriptMutex;
+
+static void ScriptFrameCleanupHandler(void)
+{
+ boost::mutex::scoped_lock lock(l_ApiScriptMutex);
+
+ std::vector<String> cleanup_keys;
+
+ typedef std::pair<String, ApiScriptFrame> KVPair;
+
+ BOOST_FOREACH(const KVPair& kv, l_ApiScriptFrames) {
+ if (kv.second.Seen < Utility::GetTime() - 1800)
+ cleanup_keys.push_back(kv.first);
+ }
+
+ BOOST_FOREACH(const String& key, cleanup_keys)
+ l_ApiScriptFrames.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);
+
+bool ConsoleHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
+{
+ if (request.RequestUrl->GetPath().size() > 3)
+ return false;
+
+ if (request.RequestMethod != "POST")
+ return false;
+
+ QueryDescription qd;
+ Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
+
+ String methodName = request.RequestUrl->GetPath()[2];
+
+ String permission = "console/" + methodName;
+ FilterUtility::CheckPermission(user, permission);
+
+ String session = HttpUtility::GetLastParameter(params, "session");
+
+ if (session.IsEmpty())
+ session = Utility::NewUniqueID();
+
+ String command = HttpUtility::GetLastParameter(params, "command");
+
+ bool sandboxed = HttpUtility::GetLastParameter(params, "sandboxed");
+
+ if (methodName == "execute-script") {
+ return ExecuteScriptHelper(request, response, command, session, sandboxed);
+ } else if (methodName == "auto-complete-script") {
+ return AutocompleteScriptHelper(request, response, command, session, sandboxed);
+ }
+
+ return true;
+}
+
+bool ConsoleHandler::ExecuteScriptHelper(HttpRequest& request, HttpResponse& response,
+ const String& command, const String& session, bool sandboxed)
+{
+ Log(LogInformation, "Console")
+ << "Executing expression: " << command;
+
+ ApiScriptFrame& lsf = l_ApiScriptFrames[session];
+ lsf.Seen = Utility::GetTime();
+
+ if (!lsf.Locals)
+ lsf.Locals = new Dictionary();
+
+ String fileName = "<" + Convert::ToString(lsf.NextLine) + ">";
+ lsf.NextLine++;
+
+ lsf.Lines[fileName] = command;
+
+ Array::Ptr results = new Array();
+ Dictionary::Ptr resultInfo = new Dictionary();
+ Expression *expr = NULL;
+ Value exprResult;
+
+ try {
+ expr = ConfigCompiler::CompileText(fileName, command);
+
+ ScriptFrame frame;
+ frame.Locals = lsf.Locals;
+ frame.Self = lsf.Locals;
+ frame.Sandboxed = sandboxed;
+
+ exprResult = expr->Evaluate(frame);
+
+ resultInfo->Set("code", 200);
+ resultInfo->Set("status", "Executed successfully.");
+ resultInfo->Set("result", Serialize(exprResult, 0));
+ } catch (const ScriptError& ex) {
+ 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";
+
+ resultInfo->Set("code", 500);
+ resultInfo->Set("status", String(msgbuf.str()));
+ resultInfo->Set("incomplete_expression", ex.IsIncompleteExpression());
+
+ Dictionary::Ptr debugInfo = new Dictionary();
+ debugInfo->Set("path", di.Path);
+ debugInfo->Set("first_line", di.FirstLine);
+ debugInfo->Set("first_column", di.FirstColumn);
+ debugInfo->Set("last_line", di.LastLine);
+ debugInfo->Set("last_column", di.LastColumn);
+ resultInfo->Set("debug_info", debugInfo);
+ } catch (...) {
+ delete expr;
+ throw;
+ }
+ delete expr;
+
+ results->Add(resultInfo);
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("results", results);
+
+ response.SetStatus(200, "OK");
+ HttpUtility::SendJsonBody(response, result);
+
+ return true;
+}
+
+bool ConsoleHandler::AutocompleteScriptHelper(HttpRequest& request, HttpResponse& response,
+ const String& command, const String& session, bool sandboxed)
+{
+ Log(LogInformation, "Console")
+ << "Auto-completing expression: " << command;
+
+ ApiScriptFrame& lsf = l_ApiScriptFrames[session];
+ lsf.Seen = Utility::GetTime();
+
+ if (!lsf.Locals)
+ lsf.Locals = new Dictionary();
+
+ Array::Ptr results = new Array();
+ Dictionary::Ptr resultInfo = new Dictionary();
+
+ ScriptFrame frame;
+ frame.Locals = lsf.Locals;
+ frame.Self = lsf.Locals;
+ frame.Sandboxed = sandboxed;
+
+ resultInfo->Set("code", 200);
+ resultInfo->Set("status", "Auto-completed successfully.");
+ resultInfo->Set("suggestions", Array::FromVector(GetAutocompletionSuggestions(command, frame)));
+
+ results->Add(resultInfo);
+
+ Dictionary::Ptr result = new Dictionary();
+ result->Set("results", results);
+
+ response.SetStatus(200, "OK");
+ HttpUtility::SendJsonBody(response, result);
+
+ return true;
+}
+
+static void AddSuggestion(std::vector<String>& matches, const String& word, const String& suggestion)
+{
+ if (suggestion.Find(word) != 0)
+ return;
+
+ matches.push_back(suggestion);
+}
+
+std::vector<String> ConsoleHandler::GetAutocompletionSuggestions(const String& word, ScriptFrame& frame)
+{
+ std::vector<String> matches;
+
+ BOOST_FOREACH(const String& keyword, ConfigWriter::GetKeywords()) {
+ AddSuggestion(matches, word, keyword);
+ }
+
+ {
+ ObjectLock olock(frame.Locals);
+ BOOST_FOREACH(const Dictionary::Pair& kv, frame.Locals) {
+ AddSuggestion(matches, word, kv.first);
+ }
+ }
+
+ {
+ ObjectLock olock(ScriptGlobal::GetGlobals());
+ BOOST_FOREACH(const Dictionary::Pair& kv, ScriptGlobal::GetGlobals()) {
+ AddSuggestion(matches, word, kv.first);
+ }
+ }
+
+ String::SizeType cperiod = word.RFind(".");
+
+ if (cperiod != -1) {
+ String pword = word.SubStr(0, cperiod);
+
+ Value value;
+
+ try {
+ Expression *expr = ConfigCompiler::CompileText("temp", pword);
+
+ if (expr)
+ value = expr->Evaluate(frame);
+
+ if (value.IsObjectType<Dictionary>()) {
+ Dictionary::Ptr dict = value;
+
+ ObjectLock olock(dict);
+ BOOST_FOREACH(const Dictionary::Pair& kv, dict) {
+ AddSuggestion(matches, word, pword + "." + kv.first);
+ }
+ }
+
+ Type::Ptr type = value.GetReflectionType();
+
+ for (int i = 0; i < type->GetFieldCount(); i++) {
+ Field field = type->GetFieldInfo(i);
+
+ AddSuggestion(matches, word, pword + "." + field.Name);
+ }
+
+ while (type) {
+ Object::Ptr prototype = type->GetPrototype();
+ Dictionary::Ptr dict = dynamic_pointer_cast<Dictionary>(prototype);
+
+ if (dict) {
+ ObjectLock olock(dict);
+ BOOST_FOREACH(const Dictionary::Pair& kv, dict) {
+ AddSuggestion(matches, word, pword + "." + kv.first);
+ }
+ }
+
+ type = type->GetBaseType();
+ }
+ } catch (...) { /* Ignore the exception */ }
+ }
+
+ return matches;
+}
--- /dev/null
+/******************************************************************************
+ * Icinga 2 *
+ * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License *
+ * as published by the Free Software Foundation; either version 2 *
+ * of the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the Free Software Foundation *
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ******************************************************************************/
+
+#ifndef CONSOLEHANDLER_H
+#define CONSOLEHANDLER_H
+
+#include "remote/httphandler.hpp"
+#include "base/scriptframe.hpp"
+
+namespace icinga
+{
+
+struct I2_REMOTE_API ApiScriptFrame
+{
+ double Seen;
+ int NextLine;
+ std::map<String, String> Lines;
+ Dictionary::Ptr Locals;
+
+ ApiScriptFrame(void)
+ : Seen(0), NextLine(1)
+ { }
+};
+
+class I2_REMOTE_API ConsoleHandler : public HttpHandler
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ConsoleHandler);
+
+ virtual bool HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response) override;
+
+ static std::vector<String> GetAutocompletionSuggestions(const String& word, ScriptFrame& frame);
+
+private:
+ static bool ExecuteScriptHelper(HttpRequest& request, HttpResponse& response,
+ const String& command, const String& session, bool sandboxed);
+ static bool AutocompleteScriptHelper(HttpRequest& request, HttpResponse& response,
+ const String& command, const String& session, bool sandboxed);
+
+};
+
+}
+
+#endif /* CONSOLEHANDLER_H */
if (srs != StatusNewItem)
return false;
- Log(LogInformation, "HttpResponse")
+ Log(LogNotice, "HttpResponse")
<< "Read " << size << " bytes";
m_Body->Write(data, size);
m_Scheme = scheme;
}
-void Url::SetAuthority(const String& username, const String& password, const String& host, const String& port)
+void Url::SetUsername(const String& username)
{
m_Username = username;
+}
+
+void Url::SetPassword(const String& password)
+{
m_Password = password;
- m_Host = host;
- m_Port = port;
}
void Url::SetHost(const String& host)
String GetFragment(void) const;
void SetScheme(const String& scheme);
- void SetAuthority(const String& username, const String& password,
- const String& host, const String& port);
+ void SetUsername(const String& username);
+ void SetPassword(const String& password);
void SetHost(const String& host);
void SetPort(const String& port);
void SetPath(const std::vector<String>& path);
void SetQuery(const std::map<String, std::vector<String> >& query);
+
void AddQueryElement(const String& name, const String& query);
void SetQueryElements(const String& name, const std::vector<String>& query);
void SetFragment(const String& fragment);
{
Url::Ptr url = new Url();
url->SetScheme("ftp");
- url->SetAuthority("Horst", "Seehofer", "koenigreich.bayern", "1918");
+ url->SetUsername("Horst");
+ url->SetPassword("Seehofer");
+ url->SetHost("koenigreich.bayern");
+ url->SetPort("1918");
std::vector<String> p = boost::assign::list_of("path")("to")("münchen");
url->SetPath(p);
BOOST_CHECK(url->Format(true) == "ftp://Horst:Seehofer@koenigreich.bayern:1918/path/to/m%C3%BCnchen");