diff --git a/itl/command-icinga.conf b/itl/command-icinga.conf index 206324a4ea5..eaacf5018ec 100644 --- a/itl/command-icinga.conf +++ b/itl/command-icinga.conf @@ -39,3 +39,7 @@ object CheckCommand "exception" { object CheckCommand "sleep" { import "sleep-check-command" } + +object CheckCommand "icinga4win-api" { + import "icinga4win-api-check-command" +} diff --git a/lib/methods/CMakeLists.txt b/lib/methods/CMakeLists.txt index 9fde9fddb35..10f736e6e98 100644 --- a/lib/methods/CMakeLists.txt +++ b/lib/methods/CMakeLists.txt @@ -9,6 +9,7 @@ set(methods_SOURCES dummychecktask.cpp dummychecktask.hpp exceptionchecktask.cpp exceptionchecktask.hpp icingachecktask.cpp icingachecktask.hpp + icinga4winapichecktask.cpp icinga4winapichecktask.hpp nullchecktask.cpp nullchecktask.hpp nulleventtask.cpp nulleventtask.hpp pluginchecktask.cpp pluginchecktask.hpp diff --git a/lib/methods/icinga4winapichecktask.cpp b/lib/methods/icinga4winapichecktask.cpp new file mode 100644 index 00000000000..b63dac1495a --- /dev/null +++ b/lib/methods/icinga4winapichecktask.cpp @@ -0,0 +1,137 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#ifndef _WIN32 +# include +#endif /* _WIN32 */ +#include "methods/icinga4winapichecktask.hpp" +#include "icinga/icingaapplication.hpp" +#include "icinga/pluginutility.hpp" +#include "base/utility.hpp" +#include "base/perfdatavalue.hpp" +#include "base/convert.hpp" +#include "base/function.hpp" +#include "base/io-engine.hpp" +#include "base/json.hpp" +#include "base/logger.hpp" +#include "base/tcpsocket.hpp" +#include "base/tlsstream.hpp" +#include +#include +#include + +using namespace icinga; + +REGISTER_FUNCTION_NONCONST(Internal, Icinga4WinApiCheck, &Icinga4WinApiCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); + +void Icinga4WinApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, + const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) +{ + using namespace boost::asio; + using namespace boost::beast; + using namespace boost::beast::http; + + REQUIRE_NOT_NULL(checkable); + REQUIRE_NOT_NULL(cr); + + CheckCommand::Ptr command = CheckCommand::ExecuteOverride ? CheckCommand::ExecuteOverride : checkable->GetCheckCommand(); + + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + MacroProcessor::ResolverList resolvers; + + if (MacroResolver::OverrideMacros) + resolvers.emplace_back("override", MacroResolver::OverrideMacros); + + if (service) + resolvers.emplace_back("service", service); + resolvers.emplace_back("host", host); + resolvers.emplace_back("command", command); + resolvers.emplace_back("icinga", IcingaApplication::GetInstance()); + + String psCommand = MacroProcessor::ResolveMacros("$icinga4win_api_command_name$", resolvers, checkable->GetLastCheckResult(), + nullptr, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros); + + Dictionary::Ptr arguments = MacroProcessor::ResolveMacros("$icinga4win_api_arguments$", resolvers, checkable->GetLastCheckResult(), + nullptr, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros); + + Dictionary::Ptr params = new Dictionary(); + + if (arguments) { + ObjectLock oLock (arguments); + Array::Ptr dummy = new Array(); + + for (auto& kv : arguments) { + Array::Ptr arg = MacroProcessor::ResolveArguments( + dummy, new Dictionary({{kv.first, kv.second}}), resolvers, + checkable->GetLastCheckResult(), resolvedMacros, useResolvedMacros + ); + + switch (arg ? arg->GetLength() : 0) { + case 0: + continue; + case 1: + params->Set(arg->Get(0), true); + break; + case 2: + params->Set(arg->Get(0), arg->Get(1)); + break; + default: { + auto k (arg->Get(0)); + + arg->Remove(0); + params->Set(k, arg); + } + } + } + } + + if (resolvedMacros && !useResolvedMacros) + return; + + ssl::context ctx (ssl::context::tls); + AsioTlsStream conn (IoEngine::Get().GetIoContext(), ctx); + request req; + flat_buffer buf; + response resp; + + req.method(verb::post); + req.target("/v1/checker?command=" + psCommand); + req.set(field::content_type, "application/json"); + req.body() = JsonEncode(params); + + Connect(conn.lowest_layer(), "127.0.0.1", "5668"); + conn.next_layer().handshake(conn.next_layer().client); + + double start = Utility::GetTime(); + + write(conn, req); + conn.flush(); + read(conn, buf, resp); + + double end = Utility::GetTime(); + + Dictionary::Ptr result = Dictionary::Ptr(JsonDecode(resp.body()))->Get(psCommand); + + if (Checkable::ExecuteCommandProcessFinishedHandler) { + ProcessResult pr; + pr.PID = -1; + pr.Output = result->Get("checkresult") + " |" + Array::Ptr(result->Get("perfdata"))->Join(""); + pr.ExecutionStart = start; + pr.ExecutionEnd = end; + pr.ExitStatus = result->Get("exitcode"); + + Checkable::ExecuteCommandProcessFinishedHandler(command->GetName(), pr); + } else { + cr->SetOutput(result->Get("checkresult")); + cr->SetPerformanceData(result->Get("perfdata")); + cr->SetState((ServiceState)(int)result->Get("exitcode")); + cr->SetExitStatus(result->Get("exitcode")); + cr->SetExecutionStart(start); + cr->SetExecutionEnd(end); + cr->SetCommand(command->GetName()); + + checkable->ProcessCheckResult(cr); + } +} diff --git a/lib/methods/icinga4winapichecktask.hpp b/lib/methods/icinga4winapichecktask.hpp new file mode 100644 index 00000000000..10824fe653e --- /dev/null +++ b/lib/methods/icinga4winapichecktask.hpp @@ -0,0 +1,30 @@ +/* Icinga 2 | (c) 2021 Icinga GmbH | GPLv2+ */ + +#ifndef ICINGA4WINAPICHECKTASK_H +#define ICINGA4WINAPICHECKTASK_H + +#include "methods/i2-methods.hpp" +#include "icinga/service.hpp" +#include "base/dictionary.hpp" + +namespace icinga +{ + +/** + * Executes checks via local Icinga for Windows API. + * + * @ingroup methods + */ +class Icinga4WinApiCheckTask +{ +public: + static void ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, + const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + +private: + Icinga4WinApiCheckTask(); +}; + +} + +#endif /* ICINGA4WINAPICHECKTASK_H */ diff --git a/lib/methods/methods-itl.conf b/lib/methods/methods-itl.conf index f9126f7ca24..07674f50565 100644 --- a/lib/methods/methods-itl.conf +++ b/lib/methods/methods-itl.conf @@ -43,6 +43,10 @@ System.assert(Internal.run_with_activation_context(function() { execute = NullCheck } + template CheckCommand "icinga4win-api-check-command" use (Icinga4WinApiCheck = Internal.Icinga4WinApiCheck) { + execute = Icinga4WinApiCheck + } + template EventCommand "null-event-command" use (NullEvent = Internal.NullEvent) { execute = NullEvent } @@ -64,6 +68,7 @@ System.assert(Internal.run_with_activation_context(function() { var methods = [ "IcingaCheck", + "Icinga4WinApiCheck", "ClusterCheck", "ClusterZoneCheck", "PluginCheck",