mirror of https://github.com/Icinga/icinga2.git
parent
a51bc4010a
commit
5d46f661ea
|
@ -27,7 +27,7 @@ endif()
|
||||||
|
|
||||||
add_executable(icinga-studio MACOSX_BUNDLE WIN32 icinga-studio.cpp
|
add_executable(icinga-studio MACOSX_BUNDLE WIN32 icinga-studio.cpp
|
||||||
forms.cpp aboutform.cpp connectform.cpp mainform.cpp
|
forms.cpp aboutform.cpp connectform.cpp mainform.cpp
|
||||||
icinga.icns apiclient.cpp ${WindowsSources})
|
icinga.icns ${WindowsSources})
|
||||||
|
|
||||||
include_directories(${Boost_INCLUDE_DIRS})
|
include_directories(${Boost_INCLUDE_DIRS})
|
||||||
target_link_libraries(icinga-studio ${Boost_LIBRARIES} ${wxWidgets_LIBRARIES} base remote)
|
target_link_libraries(icinga-studio ${Boost_LIBRARIES} ${wxWidgets_LIBRARIES} base remote)
|
||||||
|
|
|
@ -142,7 +142,6 @@ void MainForm::ObjectsCompletionHandler(boost::exception_ptr eptr, const std::ve
|
||||||
m_PropertyGrid->Clear();
|
m_PropertyGrid->Clear();
|
||||||
|
|
||||||
if (eptr) {
|
if (eptr) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boost::rethrow_exception(eptr);
|
boost::rethrow_exception(eptr);
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#ifndef MAINFORM_H
|
#ifndef MAINFORM_H
|
||||||
#define MAINFORM_H
|
#define MAINFORM_H
|
||||||
|
|
||||||
#include "icinga-studio/apiclient.hpp"
|
#include "remote/apiclient.hpp"
|
||||||
#include "remote/url.hpp"
|
#include "remote/url.hpp"
|
||||||
#include "base/exception.hpp"
|
#include "base/exception.hpp"
|
||||||
#include "icinga-studio/forms.h"
|
#include "icinga-studio/forms.h"
|
||||||
|
|
|
@ -63,7 +63,7 @@ static Object::Ptr SerializeObject(const Object::Ptr& input, int attributeTypes)
|
||||||
for (int i = 0; i < type->GetFieldCount(); i++) {
|
for (int i = 0; i < type->GetFieldCount(); i++) {
|
||||||
Field field = type->GetFieldInfo(i);
|
Field field = type->GetFieldInfo(i);
|
||||||
|
|
||||||
if ((field.Attributes & attributeTypes) == 0)
|
if (attributeTypes != 0 && (field.Attributes & attributeTypes) == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
fields->Set(field.Name, Serialize(input->GetField(i), attributeTypes));
|
fields->Set(field.Name, Serialize(input->GetField(i), attributeTypes));
|
||||||
|
|
|
@ -19,7 +19,11 @@
|
||||||
|
|
||||||
#include "cli/consolecommand.hpp"
|
#include "cli/consolecommand.hpp"
|
||||||
#include "config/configcompiler.hpp"
|
#include "config/configcompiler.hpp"
|
||||||
|
#include "remote/apiclient.hpp"
|
||||||
|
#include "remote/consolehandler.hpp"
|
||||||
|
#include "remote/url.hpp"
|
||||||
#include "base/configwriter.hpp"
|
#include "base/configwriter.hpp"
|
||||||
|
#include "base/serializer.hpp"
|
||||||
#include "base/json.hpp"
|
#include "base/json.hpp"
|
||||||
#include "base/console.hpp"
|
#include "base/console.hpp"
|
||||||
#include "base/application.hpp"
|
#include "base/application.hpp"
|
||||||
|
@ -37,6 +41,8 @@ using namespace icinga;
|
||||||
namespace po = boost::program_options;
|
namespace po = boost::program_options;
|
||||||
|
|
||||||
static ScriptFrame *l_ScriptFrame;
|
static ScriptFrame *l_ScriptFrame;
|
||||||
|
static ApiClient::Ptr l_ApiClient;
|
||||||
|
static String l_Session;
|
||||||
|
|
||||||
REGISTER_CLICOMMAND("console", ConsoleCommand);
|
REGISTER_CLICOMMAND("console", ConsoleCommand);
|
||||||
|
|
||||||
|
@ -60,89 +66,41 @@ void ConsoleCommand::InitParameters(boost::program_options::options_description&
|
||||||
{
|
{
|
||||||
visibleDesc.add_options()
|
visibleDesc.add_options()
|
||||||
("connect,c", po::value<std::string>(), "connect to an Icinga 2 instance")
|
("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")
|
("sandbox", "enable sandbox mode")
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_EDITLINE
|
#ifdef HAVE_EDITLINE
|
||||||
static void AddSuggestion(std::vector<String>& matches, const String& word, const String& suggestion)
|
char *ConsoleCommand::ConsoleCompleteHelper(const char *word, int state)
|
||||||
{
|
|
||||||
if (suggestion.Find(word) != 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
matches.push_back(suggestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *ConsoleCompleteHelper(const char *word, int state)
|
|
||||||
{
|
{
|
||||||
static std::vector<String> matches;
|
static std::vector<String> matches;
|
||||||
String aword = word;
|
|
||||||
|
|
||||||
if (state == 0) {
|
if (state == 0) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
matches.clear();
|
matches.clear();
|
||||||
|
|
||||||
BOOST_FOREACH(const String& keyword, ConfigWriter::GetKeywords()) {
|
ObjectLock olock(suggestions);
|
||||||
AddSuggestion(matches, word, keyword);
|
std::copy(suggestions->Begin(), suggestions->End(), std::back_inserter(matches));
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 */ }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,30 +122,53 @@ int ConsoleCommand::Run(const po::variables_map& vm, const std::vector<std::stri
|
||||||
int next_line = 1;
|
int next_line = 1;
|
||||||
|
|
||||||
#ifdef HAVE_EDITLINE
|
#ifdef HAVE_EDITLINE
|
||||||
rl_completion_entry_function = ConsoleCompleteHelper;
|
rl_completion_entry_function = ConsoleCommand::ConsoleCompleteHelper;
|
||||||
rl_completion_append_character = '\0';
|
rl_completion_append_character = '\0';
|
||||||
#endif /* HAVE_EDITLINE */
|
#endif /* HAVE_EDITLINE */
|
||||||
|
|
||||||
String addr, session;
|
String addr;
|
||||||
ScriptFrame scriptFrame;
|
ScriptFrame scriptFrame;
|
||||||
|
|
||||||
l_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")) {
|
if (vm.count("connect")) {
|
||||||
addr = vm["connect"].as<std::string>();
|
addr = vm["connect"].as<std::string>();
|
||||||
session = Utility::NewUniqueID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vm.count("sandbox")) {
|
if (!addr.IsEmpty()) {
|
||||||
if (vm.count("connect")) {
|
Url::Ptr url;
|
||||||
Log(LogCritical, "ConsoleCommand", "Sandbox mode cannot be used together with --connect.");
|
|
||||||
|
try {
|
||||||
|
url = new Url(addr);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
Log(LogCritical, "ConsoleCommand", ex.what());
|
||||||
return EXIT_FAILURE;
|
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()) {
|
while (std::cin.good()) {
|
||||||
String fileName = "<" + Convert::ToString(next_line) + ">";
|
String fileName = "<" + Convert::ToString(next_line) + ">";
|
||||||
|
@ -197,6 +178,9 @@ int ConsoleCommand::Run(const po::variables_map& vm, const std::vector<std::stri
|
||||||
std::string command;
|
std::string command;
|
||||||
|
|
||||||
incomplete:
|
incomplete:
|
||||||
|
std::string line;
|
||||||
|
|
||||||
|
if (!vm.count("eval")) {
|
||||||
#ifdef HAVE_EDITLINE
|
#ifdef HAVE_EDITLINE
|
||||||
std::ostringstream promptbuf;
|
std::ostringstream promptbuf;
|
||||||
std::ostream& os = promptbuf;
|
std::ostream& os = promptbuf;
|
||||||
|
@ -222,35 +206,59 @@ incomplete:
|
||||||
|
|
||||||
add_history(cline);
|
add_history(cline);
|
||||||
|
|
||||||
std::string line = cline;
|
line = cline;
|
||||||
|
|
||||||
free(cline);
|
free(cline);
|
||||||
#else /* HAVE_EDITLINE */
|
#else /* HAVE_EDITLINE */
|
||||||
std::string line;
|
|
||||||
std::getline(std::cin, line);
|
std::getline(std::cin, line);
|
||||||
#endif /* HAVE_EDITLINE */
|
#endif /* HAVE_EDITLINE */
|
||||||
|
} else
|
||||||
|
line = vm["eval"].as<std::string>();
|
||||||
|
|
||||||
if (!command.empty())
|
if (!command.empty())
|
||||||
command += "\n";
|
command += "\n";
|
||||||
|
|
||||||
command += line;
|
command += line;
|
||||||
|
|
||||||
if (addr.IsEmpty()) {
|
|
||||||
Expression *expr = NULL;
|
Expression *expr = NULL;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
lines[fileName] = command;
|
lines[fileName] = command;
|
||||||
|
|
||||||
expr = ConfigCompiler::CompileText(fileName, command);
|
Value result;
|
||||||
|
|
||||||
if (expr) {
|
if (!l_ApiClient) {
|
||||||
Value result = expr->Evaluate(scriptFrame);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eptr)
|
||||||
|
boost::rethrow_exception(eptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vm.count("eval")) {
|
||||||
std::cout << ConsoleColorTag(Console_ForegroundCyan);
|
std::cout << ConsoleColorTag(Console_ForegroundCyan);
|
||||||
if (!result.IsObject() || result.IsObjectType<Array>() || result.IsObjectType<Dictionary>())
|
ConfigWriter::EmitValue(std::cout, 1, result);
|
||||||
std::cout << JsonEncode(result);
|
|
||||||
else
|
|
||||||
std::cout << result;
|
|
||||||
std::cout << ConsoleColorTag(Console_Normal) << "\n";
|
std::cout << ConsoleColorTag(Console_Normal) << "\n";
|
||||||
|
} else {
|
||||||
|
std::cout << JsonEncode(result) << "\n";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (const ScriptError& ex) {
|
} catch (const ScriptError& ex) {
|
||||||
if (ex.IsIncompleteExpression()) {
|
if (ex.IsIncompleteExpression()) {
|
||||||
|
@ -297,48 +305,64 @@ incomplete:
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << ex.what() << "\n";
|
std::cout << ex.what() << "\n";
|
||||||
|
|
||||||
|
if (vm.count("eval"))
|
||||||
|
return EXIT_FAILURE;
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
std::cout << "Error: " << DiagnosticInformation(ex) << "\n";
|
std::cout << "Error: " << DiagnosticInformation(ex) << "\n";
|
||||||
|
|
||||||
|
if (vm.count("eval"))
|
||||||
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete expr;
|
delete expr;
|
||||||
} else {
|
|
||||||
Socket::Ptr socket;
|
|
||||||
|
|
||||||
#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 */
|
|
||||||
|
|
||||||
String query = "SCRIPT " + session + "\n" + line + "\n\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), true);
|
|
||||||
result += String(buf, buf + rc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.GetLength() < 16) {
|
return EXIT_SUCCESS;
|
||||||
Log(LogCritical, "ConsoleCommand", "Received invalid response from Livestatus.");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << result.SubStr(16) << "\n";
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
resultOut = result;
|
||||||
|
|
||||||
|
{
|
||||||
|
boost::mutex::scoped_lock lock(mutex);
|
||||||
|
ready = true;
|
||||||
|
cv.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultOut = result;
|
||||||
|
|
||||||
|
{
|
||||||
|
boost::mutex::scoped_lock lock(mutex);
|
||||||
|
ready = true;
|
||||||
|
cv.notify_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#define CONSOLECOMMAND_H
|
#define CONSOLECOMMAND_H
|
||||||
|
|
||||||
#include "cli/clicommand.hpp"
|
#include "cli/clicommand.hpp"
|
||||||
|
#include "base/exception.hpp"
|
||||||
|
|
||||||
namespace icinga
|
namespace icinga
|
||||||
{
|
{
|
||||||
|
@ -41,6 +42,22 @@ public:
|
||||||
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
|
virtual void InitParameters(boost::program_options::options_description& visibleDesc,
|
||||||
boost::program_options::options_description& hiddenDesc) const override;
|
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;
|
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 */
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
#include "livestatus/orfilter.hpp"
|
#include "livestatus/orfilter.hpp"
|
||||||
#include "livestatus/andfilter.hpp"
|
#include "livestatus/andfilter.hpp"
|
||||||
#include "icinga/externalcommandprocessor.hpp"
|
#include "icinga/externalcommandprocessor.hpp"
|
||||||
#include "config/configcompiler.hpp"
|
|
||||||
#include "base/debug.hpp"
|
#include "base/debug.hpp"
|
||||||
#include "base/convert.hpp"
|
#include "base/convert.hpp"
|
||||||
#include "base/objectlock.hpp"
|
#include "base/objectlock.hpp"
|
||||||
|
@ -52,36 +51,6 @@ using namespace icinga;
|
||||||
|
|
||||||
static int l_ExternalCommands = 0;
|
static int l_ExternalCommands = 0;
|
||||||
static boost::mutex l_QueryMutex;
|
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)
|
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),
|
: m_KeepAlive(false), m_OutputFormat("csv"), m_ColumnHeaders(true), m_Limit(-1), m_ErrorCode(0),
|
||||||
|
@ -123,16 +92,6 @@ LivestatusQuery::LivestatusQuery(const std::vector<String>& lines, const String&
|
||||||
if (m_Verb == "COMMAND") {
|
if (m_Verb == "COMMAND") {
|
||||||
m_KeepAlive = true;
|
m_KeepAlive = true;
|
||||||
m_Command = target;
|
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") {
|
} else if (m_Verb == "GET") {
|
||||||
m_Table = target;
|
m_Table = target;
|
||||||
} else {
|
} else {
|
||||||
|
@ -625,54 +584,6 @@ void LivestatusQuery::ExecuteCommandHelper(const Stream::Ptr& stream)
|
||||||
SendResponse(stream, LivestatusErrorOK, "");
|
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)
|
void LivestatusQuery::ExecuteErrorHelper(const Stream::Ptr& stream)
|
||||||
{
|
{
|
||||||
Log(LogDebug, "LivestatusQuery")
|
Log(LogDebug, "LivestatusQuery")
|
||||||
|
@ -720,8 +631,6 @@ bool LivestatusQuery::Execute(const Stream::Ptr& stream)
|
||||||
ExecuteGetHelper(stream);
|
ExecuteGetHelper(stream);
|
||||||
else if (m_Verb == "COMMAND")
|
else if (m_Verb == "COMMAND")
|
||||||
ExecuteCommandHelper(stream);
|
ExecuteCommandHelper(stream);
|
||||||
else if (m_Verb == "SCRIPT")
|
|
||||||
ExecuteScriptHelper(stream);
|
|
||||||
else if (m_Verb == "ERROR")
|
else if (m_Verb == "ERROR")
|
||||||
ExecuteErrorHelper(stream);
|
ExecuteErrorHelper(stream);
|
||||||
else
|
else
|
||||||
|
|
|
@ -40,18 +40,6 @@ enum LivestatusError
|
||||||
LivestatusErrorQuery = 452
|
LivestatusErrorQuery = 452
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LivestatusScriptFrame
|
|
||||||
{
|
|
||||||
double Seen;
|
|
||||||
int NextLine;
|
|
||||||
std::map<String, String> Lines;
|
|
||||||
Dictionary::Ptr Locals;
|
|
||||||
|
|
||||||
LivestatusScriptFrame(void)
|
|
||||||
: Seen(0), NextLine(1)
|
|
||||||
{ }
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ingroup livestatus
|
* @ingroup livestatus
|
||||||
*/
|
*/
|
||||||
|
@ -106,7 +94,6 @@ private:
|
||||||
|
|
||||||
void ExecuteGetHelper(const Stream::Ptr& stream);
|
void ExecuteGetHelper(const Stream::Ptr& stream);
|
||||||
void ExecuteCommandHelper(const Stream::Ptr& stream);
|
void ExecuteCommandHelper(const Stream::Ptr& stream);
|
||||||
void ExecuteScriptHelper(const Stream::Ptr& stream);
|
|
||||||
void ExecuteErrorHelper(const Stream::Ptr& stream);
|
void ExecuteErrorHelper(const Stream::Ptr& stream);
|
||||||
|
|
||||||
void SendResponse(const Stream::Ptr& stream, int code, const String& data);
|
void SendResponse(const Stream::Ptr& stream, int code, const String& data);
|
||||||
|
|
|
@ -21,10 +21,10 @@ mkclass_target(endpoint.ti endpoint.tcpp endpoint.thpp)
|
||||||
mkclass_target(zone.ti zone.tcpp zone.thpp)
|
mkclass_target(zone.ti zone.tcpp zone.thpp)
|
||||||
|
|
||||||
set(remote_SOURCES
|
set(remote_SOURCES
|
||||||
actionshandler.cpp apiaction.cpp
|
actionshandler.cpp apiaction.cpp apiclient.cpp
|
||||||
apifunction.cpp apilistener.cpp apilistener.thpp apilistener-configsync.cpp
|
apifunction.cpp apilistener.cpp apilistener.thpp apilistener-configsync.cpp
|
||||||
apilistener-filesync.cpp apiuser.cpp apiuser.thpp authority.cpp base64.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
|
configstageshandler.cpp createobjecthandler.cpp deleteobjecthandler.cpp
|
||||||
endpoint.cpp endpoint.thpp eventshandler.cpp eventqueue.cpp filterutility.cpp
|
endpoint.cpp endpoint.thpp eventshandler.cpp eventqueue.cpp filterutility.cpp
|
||||||
httpchunkedencoding.cpp httpclientconnection.cpp httpserverconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
|
httpchunkedencoding.cpp httpclientconnection.cpp httpserverconnection.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
* 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 "remote/base64.hpp"
|
||||||
#include "base/json.hpp"
|
#include "base/json.hpp"
|
||||||
#include "base/logger.hpp"
|
#include "base/logger.hpp"
|
||||||
|
@ -116,7 +116,6 @@ void ApiClient::GetObjects(const String& pluralType, const ObjectsCompletionCall
|
||||||
path.push_back("objects");
|
path.push_back("objects");
|
||||||
path.push_back(pluralType);
|
path.push_back(pluralType);
|
||||||
url->SetPath(path);
|
url->SetPath(path);
|
||||||
String qp;
|
|
||||||
|
|
||||||
std::map<String, std::vector<String> > params;
|
std::map<String, std::vector<String> > params;
|
||||||
|
|
||||||
|
@ -206,3 +205,159 @@ void ApiClient::ObjectsHttpCompletionCallback(HttpRequest& request,
|
||||||
callback(boost::current_exception(), std::vector<ApiObject::Ptr>());
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -96,6 +96,14 @@ public:
|
||||||
const std::vector<String>& names = std::vector<String>(),
|
const std::vector<String>& names = std::vector<String>(),
|
||||||
const std::vector<String>& attrs = std::vector<String>()) const;
|
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:
|
private:
|
||||||
HttpClientConnection::Ptr m_Connection;
|
HttpClientConnection::Ptr m_Connection;
|
||||||
String m_User;
|
String m_User;
|
||||||
|
@ -105,6 +113,10 @@ private:
|
||||||
HttpResponse& response, const TypesCompletionCallback& callback);
|
HttpResponse& response, const TypesCompletionCallback& callback);
|
||||||
static void ObjectsHttpCompletionCallback(HttpRequest& request,
|
static void ObjectsHttpCompletionCallback(HttpRequest& request,
|
||||||
HttpResponse& response, const ObjectsCompletionCallback& callback);
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,290 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* 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;
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* 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 */
|
|
@ -192,7 +192,7 @@ bool HttpResponse::Parse(StreamReadContext& src, bool may_wait)
|
||||||
if (srs != StatusNewItem)
|
if (srs != StatusNewItem)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Log(LogInformation, "HttpResponse")
|
Log(LogNotice, "HttpResponse")
|
||||||
<< "Read " << size << " bytes";
|
<< "Read " << size << " bytes";
|
||||||
|
|
||||||
m_Body->Write(data, size);
|
m_Body->Write(data, size);
|
||||||
|
|
|
@ -177,12 +177,14 @@ void Url::SetScheme(const String& scheme)
|
||||||
m_Scheme = scheme;
|
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;
|
m_Username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Url::SetPassword(const String& password)
|
||||||
|
{
|
||||||
m_Password = password;
|
m_Password = password;
|
||||||
m_Host = host;
|
|
||||||
m_Port = port;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Url::SetHost(const String& host)
|
void Url::SetHost(const String& host)
|
||||||
|
|
|
@ -59,12 +59,13 @@ public:
|
||||||
String GetFragment(void) const;
|
String GetFragment(void) const;
|
||||||
|
|
||||||
void SetScheme(const String& scheme);
|
void SetScheme(const String& scheme);
|
||||||
void SetAuthority(const String& username, const String& password,
|
void SetUsername(const String& username);
|
||||||
const String& host, const String& port);
|
void SetPassword(const String& password);
|
||||||
void SetHost(const String& host);
|
void SetHost(const String& host);
|
||||||
void SetPort(const String& port);
|
void SetPort(const String& port);
|
||||||
void SetPath(const std::vector<String>& path);
|
void SetPath(const std::vector<String>& path);
|
||||||
void SetQuery(const std::map<String, std::vector<String> >& query);
|
void SetQuery(const std::map<String, std::vector<String> >& query);
|
||||||
|
|
||||||
void AddQueryElement(const String& name, const String& query);
|
void AddQueryElement(const String& name, const String& query);
|
||||||
void SetQueryElements(const String& name, const std::vector<String>& query);
|
void SetQueryElements(const String& name, const std::vector<String>& query);
|
||||||
void SetFragment(const String& fragment);
|
void SetFragment(const String& fragment);
|
||||||
|
|
|
@ -47,7 +47,10 @@ BOOST_AUTO_TEST_CASE(get_and_set)
|
||||||
{
|
{
|
||||||
Url::Ptr url = new Url();
|
Url::Ptr url = new Url();
|
||||||
url->SetScheme("ftp");
|
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");
|
std::vector<String> p = boost::assign::list_of("path")("to")("münchen");
|
||||||
url->SetPath(p);
|
url->SetPath(p);
|
||||||
BOOST_CHECK(url->Format(true) == "ftp://Horst:Seehofer@koenigreich.bayern:1918/path/to/m%C3%BCnchen");
|
BOOST_CHECK(url->Format(true) == "ftp://Horst:Seehofer@koenigreich.bayern:1918/path/to/m%C3%BCnchen");
|
||||||
|
|
Loading…
Reference in New Issue