MainForm::MainForm(wxWindow *parent, const Url::Ptr& url) : MainFormBase(parent) { #ifdef _WIN32 SetIcon(wxICON(icinga)); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); #endif /* _WIN32 */ String port = url->GetPort(); if (port.IsEmpty()) port = "5665"; m_ApiClient = new ApiClient(url->GetHost(), port, url->GetUsername(), url->GetPassword()); m_ApiClient->GetTypes(boost::bind(&MainForm::TypesCompletionHandler, this, _1, _2, true)); std::string title = url->Format() + " - Icinga Studio"; SetTitle(title); m_ObjectsList->InsertColumn(0, "Name", 0, 300); m_PropertyGrid->SetColumnCount(3); }
/** * Sends the request via REST API and returns the parsed response. * * @param tlsStream Caller must prepare TLS stream/handshake. * @param url Fully prepared Url object. * @return A dictionary decoded from JSON. */ Dictionary::Ptr ConsoleCommand::SendRequest() { namespace beast = boost::beast; namespace http = beast::http; l_TlsStream = ConsoleCommand::Connect(); Defer s ([&]() { l_TlsStream->next_layer().shutdown(); }); http::request<http::string_body> request(http::verb::post, std::string(l_Url->Format(false)), 10); request.set(http::field::user_agent, "Icinga/DebugConsole/" + Application::GetAppVersion()); request.set(http::field::host, l_Url->GetHost() + ":" + l_Url->GetPort()); request.set(http::field::accept, "application/json"); request.set(http::field::authorization, "Basic " + Base64::Encode(l_Url->GetUsername() + ":" + l_Url->GetPassword())); try { http::write(*l_TlsStream, request); l_TlsStream->flush(); } catch (const std::exception &ex) { Log(LogWarning, "DebugConsole") << "Cannot write HTTP request to REST API at URL '" << l_Url->Format(true) << "': " << ex.what(); throw; } http::parser<false, http::string_body> parser; beast::flat_buffer buf; try { http::read(*l_TlsStream, buf, parser); } catch (const std::exception &ex) { Log(LogWarning, "DebugConsole") << "Failed to parse HTTP response from REST API at URL '" << l_Url->Format(true) << "': " << ex.what(); throw; } auto &response(parser.get()); /* Handle HTTP errors first. */ if (response.result() != http::status::ok) { String message = "HTTP request failed; Code: " + Convert::ToString(response.result()) + "; Body: " + response.body(); BOOST_THROW_EXCEPTION(ScriptError(message)); } Dictionary::Ptr jsonResponse; auto &body(response.body()); //Log(LogWarning, "Console") // << "Got response: " << response.body(); try { jsonResponse = JsonDecode(body); } catch (...) { String message = "Cannot parse JSON response body: " + response.body(); BOOST_THROW_EXCEPTION(ScriptError(message)); } return jsonResponse; }
int ConsoleCommand::RunScriptConsole(ScriptFrame& scriptFrame, const String& addr, const String& session, const String& commandOnce, const String& commandOnceFileName, bool syntaxOnly) { std::map<String, String> lines; int next_line = 1; #ifdef HAVE_EDITLINE char *homeEnv = getenv("HOME"); String historyPath; std::fstream historyfp; if (homeEnv) { historyPath = String(homeEnv) + "/.icinga2_history"; historyfp.open(historyPath.CStr(), std::fstream::in); String line; while (std::getline(historyfp, line.GetData())) add_history(line.CStr()); historyfp.close(); } #endif /* HAVE_EDITLINE */ l_ScriptFrame = &scriptFrame; l_Session = session; if (!addr.IsEmpty()) { Url::Ptr url; try { url = new Url(addr); } catch (const std::exception& ex) { Log(LogCritical, "ConsoleCommand", ex.what()); return EXIT_FAILURE; } const char *usernameEnv = getenv("ICINGA2_API_USERNAME"); const char *passwordEnv = getenv("ICINGA2_API_PASSWORD"); 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; if (commandOnceFileName.IsEmpty()) fileName = "<" + Convert::ToString(next_line) + ">"; else fileName = commandOnceFileName; next_line++; bool continuation = false; std::string command; incomplete: std::string line; if (commandOnce.IsEmpty()) { #ifdef HAVE_EDITLINE std::ostringstream promptbuf; std::ostream& os = promptbuf; #else /* HAVE_EDITLINE */ std::ostream& os = std::cout; #endif /* HAVE_EDITLINE */ os << fileName; if (!continuation) os << " => "; else os << " .. "; #ifdef HAVE_EDITLINE String prompt = promptbuf.str(); char *cline; cline = readline(prompt.CStr()); if (!cline) break; if (commandOnce.IsEmpty() && cline[0] != '\0') { add_history(cline); if (!historyPath.IsEmpty()) { historyfp.open(historyPath.CStr(), std::fstream::out | std::fstream::app); historyfp << cline << "\n"; historyfp.close(); } } line = cline; free(cline); #else /* HAVE_EDITLINE */ std::getline(std::cin, line); #endif /* HAVE_EDITLINE */ } else line = commandOnce; if (!line.empty() && line[0] == '$') { if (line == "$continue" || line == "$quit" || line == "$exit") break; else if (line == "$help") std::cout << "Welcome to the Icinga 2 debug console.\n" "Usable commands:\n" " $continue Continue running Icinga 2 (script debugger).\n" " $quit, $exit Stop debugging and quit the console.\n" " $help Print this help.\n\n" "For more information on how to use this console, please consult the documentation at https://icinga.com/docs\n"; else std::cout << "Unknown debugger command: " << line << "\n"; continue; } if (!command.empty()) command += "\n"; command += line; std::unique_ptr<Expression> expr; try { lines[fileName] = command; Value result; if (!l_ApiClient) { expr = ConfigCompiler::CompileText(fileName, command); /* This relies on the fact that - for syntax errors - CompileText() * returns an AST where the top-level expression is a 'throw'. */ if (!syntaxOnly || dynamic_cast<ThrowExpression *>(expr.get())) { if (syntaxOnly) std::cerr << " => " << command << std::endl; result = Serialize(expr->Evaluate(scriptFrame), 0); } else result = true; } else { boost::mutex mutex; boost::condition_variable cv; bool ready = false; boost::exception_ptr eptr; l_ApiClient->ExecuteScript(l_Session, command, scriptFrame.Sandboxed, std::bind(&ConsoleCommand::ExecuteScriptCompletionHandler, std::ref(mutex), std::ref(cv), std::ref(ready), _1, _2, std::ref(result), std::ref(eptr))); { boost::mutex::scoped_lock lock(mutex); while (!ready) cv.wait(lock); } if (eptr) boost::rethrow_exception(eptr); } if (commandOnce.IsEmpty()) { 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() && commandOnce.IsEmpty()) { continuation = true; goto incomplete; } DebugInfo di = ex.GetDebugInfo(); if (commandOnceFileName.IsEmpty() && lines.find(di.Path) != lines.end()) { String text = lines[di.Path]; std::vector<String> ulines = text.Split("\n"); for (int i = 1; i <= ulines.size(); i++) { int start, len; if (i == di.FirstLine) start = di.FirstColumn; else start = 0; if (i == di.LastLine) len = di.LastColumn - di.FirstColumn + 1; else len = ulines[i - 1].GetLength(); int offset; 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 { ShowCodeLocation(std::cout, di); } std::cout << ex.what() << "\n"; if (!commandOnce.IsEmpty()) return EXIT_FAILURE; } catch (const std::exception& ex) { std::cout << "Error: " << DiagnosticInformation(ex) << "\n"; if (!commandOnce.IsEmpty()) return EXIT_FAILURE; } } return EXIT_SUCCESS; }