diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md
index b8775c74bce..ca06837c881 100644
--- a/doc/10-icinga-template-library.md
+++ b/doc/10-icinga-template-library.md
@@ -187,6 +187,56 @@ Name | Description
----------------|--------------
sleep\_time | **Optional.** The duration of the sleep in seconds. Defaults to 1s.
+### ifw-api
+
+Built-in check command for executing arbitrary PowerShell check commands via the
+[Icinga for Windows REST API](https://icinga.com/docs/icinga-for-windows/latest/doc/110-Installation/30-API-Check-Forwarder/).
+Consult that documentation for why and how to optimally use the `ifw-api`
+command as an addon for existing Icinga clusters with Icinga for Windows.
+
+In short, that feature lets the PowerShell processes spawned by Icinga just
+talk to the pre-loaded IfW API instead of loading all PowerShell check commands
+by itself on every check. In contrast, the `ifw-api` command doesn't even spawn
+any process, but communicates directly with the IfW API.
+
+It may be also used like e.g. [check_by_ssh](#plugin-check-command-by-ssh).
+Its custom variables provide high flexibility.
+From using a custom CA to controlling the IfW API directly from a Linux satellite.
+
+Optional custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters):
+
+| Name | Default | Description |
+|-------------------------|-------------------|-------------------------------------------------------------------------------------------------------------|
+| ifw\_api\_command | `$command.name$` | Command to run. |
+| ifw\_api\_arguments | {} (none) | Arguments for the command, similar to [CheckCommand](09-object-types.md#objecttype-checkcommand)#arguments. |
+| ifw\_api\_host | null (localhost) | IfW API host. |
+| ifw\_api\_port | 5668 | IfW API port. |
+| ifw\_api\_expected\_san | `$ifw_api_host$` | Peer TLS certificate SAN (and SNI). null means agent NodeName. |
+| ifw\_api\_cert | null (Icinga PKI) | TLS client certificate path. |
+| ifw\_api\_key | null (Icinga PKI) | TLS client private key path. |
+| ifw\_api\_ca | null (Icinga PKI) | Peer TLS CA certificate path. |
+| ifw\_api\_crl | null (Icinga PKI) | Path to TLS CRL to check peer against. |
+| ifw\_api\_username | null (none) | Basic auth username. |
+| ifw\_api\_password | null (none) | Basic auth password. |
+
+!!! info
+
+ Due to how Icinga 2 resolves macros and serializes the resolved values for
+ sending to a command endpoint (if any), ifw\_api\_arguments may not directly
+ contain functions for the case `ifw-api` is used with command endpoints. Only
+ macro strings referring to custom variables which are set to functions work.
+
+#### Remarks
+
+* `$command.name$` is resolved at runtime to the name of the specific
+ check command being run and not any of the templates it imports, i.e. it
+ becomes e.g. "Invoke-IcingaCheckCPU" if "ifw-api" is imported there
+* `ifw-api` connects to localhost (if ifw\_api\_host is null), but expects
+ the peer to identify itself via TLS with the NodeName of the endpoint
+ actually running the command (if ifw\_api\_expected\_san is null)
+* The actual values of ifw\_api\_cert, ifw\_api\_key, ifw\_api\_ca and ifw\_api\_crl
+ are also resolved to the Icinga PKI on the command endpoint if null
+
diff --git a/itl/command-icinga.conf b/itl/command-icinga.conf
index 206324a4ea5..74523a4b616 100644
--- a/itl/command-icinga.conf
+++ b/itl/command-icinga.conf
@@ -39,3 +39,19 @@ object CheckCommand "exception" {
object CheckCommand "sleep" {
import "sleep-check-command"
}
+
+object CheckCommand "ifw-api" {
+ import "ifw-api-check-command"
+
+ vars.ifw_api_command = "$command.name$"
+ vars.ifw_api_arguments = {}
+ vars.ifw_api_host = null
+ vars.ifw_api_port = 5668
+ vars.ifw_api_expected_san = "$ifw_api_host$"
+ vars.ifw_api_cert = null
+ vars.ifw_api_key = null
+ vars.ifw_api_ca = null
+ vars.ifw_api_crl = null
+ vars.ifw_api_username = null
+ vars.ifw_api_password = null
+}
diff --git a/lib/methods/CMakeLists.txt b/lib/methods/CMakeLists.txt
index 9fde9fddb35..a7c3090b894 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
+ ifwapichecktask.cpp ifwapichecktask.hpp
nullchecktask.cpp nullchecktask.hpp
nulleventtask.cpp nulleventtask.hpp
pluginchecktask.cpp pluginchecktask.hpp
diff --git a/lib/methods/ifwapichecktask.cpp b/lib/methods/ifwapichecktask.cpp
new file mode 100644
index 00000000000..8516d70c033
--- /dev/null
+++ b/lib/methods/ifwapichecktask.cpp
@@ -0,0 +1,531 @@
+/* Icinga 2 | (c) 2023 Icinga GmbH | GPLv2+ */
+
+#ifndef _WIN32
+# include
+#endif /* _WIN32 */
+#include "methods/ifwapichecktask.hpp"
+#include "methods/pluginchecktask.hpp"
+#include "icinga/checkresult-ti.hpp"
+#include "icinga/icingaapplication.hpp"
+#include "icinga/pluginutility.hpp"
+#include "base/base64.hpp"
+#include "base/defer.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/shared.hpp"
+#include "base/tcpsocket.hpp"
+#include "base/tlsstream.hpp"
+#include "remote/apilistener.hpp"
+#include "remote/url.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace icinga;
+
+REGISTER_FUNCTION_NONCONST(Internal, IfwApiCheck, &IfwApiCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros");
+
+static void ReportIfwCheckResult(
+ const Checkable::Ptr& checkable, const Value& cmdLine, const CheckResult::Ptr& cr,
+ const String& output, double start, double end, int exitcode = 3, const Array::Ptr& perfdata = nullptr
+)
+{
+ if (Checkable::ExecuteCommandProcessFinishedHandler) {
+ ProcessResult pr;
+ pr.PID = -1;
+ pr.Output = perfdata ? output + " |" + String(perfdata->Join(" ")) : output;
+ pr.ExecutionStart = start;
+ pr.ExecutionEnd = end;
+ pr.ExitStatus = exitcode;
+
+ Checkable::ExecuteCommandProcessFinishedHandler(cmdLine, pr);
+ } else {
+ auto splittedPerfdata (perfdata);
+
+ if (perfdata) {
+ splittedPerfdata = new Array();
+ ObjectLock oLock (perfdata);
+
+ for (String pv : perfdata) {
+ PluginUtility::SplitPerfdata(pv)->CopyTo(splittedPerfdata);
+ }
+ }
+
+ cr->SetOutput(output);
+ cr->SetPerformanceData(splittedPerfdata);
+ cr->SetState((ServiceState)exitcode);
+ cr->SetExitStatus(exitcode);
+ cr->SetExecutionStart(start);
+ cr->SetExecutionEnd(end);
+ cr->SetCommand(cmdLine);
+
+ checkable->ProcessCheckResult(cr);
+ }
+}
+
+static void ReportIfwCheckResult(
+ boost::asio::yield_context yc, const Checkable::Ptr& checkable, const Value& cmdLine,
+ const CheckResult::Ptr& cr, const String& output, double start
+)
+{
+ double end = Utility::GetTime();
+ CpuBoundWork cbw (yc);
+
+ ReportIfwCheckResult(checkable, cmdLine, cr, output, start, end);
+}
+
+static const char* GetUnderstandableError(const std::exception& ex)
+{
+ auto se (dynamic_cast(&ex));
+
+ if (se && se->code() == boost::asio::error::operation_aborted) {
+ return "Timeout exceeded";
+ }
+
+ return ex.what();
+}
+
+static void DoIfwNetIo(
+ boost::asio::yield_context yc, const Checkable::Ptr& checkable, const Array::Ptr& cmdLine,
+ const CheckResult::Ptr& cr, const String& psCommand, const String& psHost, const String& san, const String& psPort,
+ AsioTlsStream& conn, boost::beast::http::request& req, double start
+)
+{
+ namespace http = boost::beast::http;
+
+ boost::beast::flat_buffer buf;
+ http::response resp;
+
+ try {
+ Connect(conn.lowest_layer(), psHost, psPort, yc);
+ } catch (const std::exception& ex) {
+ ReportIfwCheckResult(
+ yc, checkable, cmdLine, cr,
+ "Can't connect to IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex),
+ start
+ );
+ return;
+ }
+
+ auto& sslConn (conn.next_layer());
+
+ try {
+ sslConn.async_handshake(conn.next_layer().client, yc);
+ } catch (const std::exception& ex) {
+ ReportIfwCheckResult(
+ yc, checkable, cmdLine, cr,
+ "TLS handshake with IfW API on host '" + psHost + "' (SNI: '" + san
+ + "') port '" + psPort + "' failed: " + GetUnderstandableError(ex),
+ start
+ );
+ return;
+ }
+
+ if (!sslConn.IsVerifyOK()) {
+ auto cert (sslConn.GetPeerCertificate());
+ Value cn;
+
+ try {
+ cn = GetCertificateCN(cert);
+ } catch (const std::exception&) {
+ }
+
+ ReportIfwCheckResult(
+ yc, checkable, cmdLine, cr,
+ "Certificate validation failed for IfW API on host '" + psHost + "' (SNI: '" + san + "'; CN: "
+ + (cn.IsString() ? "'" + cn + "'" : "N/A") + ") port '" + psPort + "': " + sslConn.GetVerifyError(),
+ start
+ );
+ return;
+ }
+
+ try {
+ http::async_write(conn, req, yc);
+ conn.async_flush(yc);
+ } catch (const std::exception& ex) {
+ ReportIfwCheckResult(
+ yc, checkable, cmdLine, cr,
+ "Can't send HTTP request to IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex),
+ start
+ );
+ return;
+ }
+
+ try {
+ http::async_read(conn, buf, resp, yc);
+ } catch (const std::exception& ex) {
+ ReportIfwCheckResult(
+ yc, checkable, cmdLine, cr,
+ "Can't read HTTP response from IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex),
+ start
+ );
+ return;
+ }
+
+ double end = Utility::GetTime();
+
+ {
+ boost::system::error_code ec;
+ sslConn.async_shutdown(yc[ec]);
+ }
+
+ CpuBoundWork cbw (yc);
+ Value jsonRoot;
+
+ try {
+ jsonRoot = JsonDecode(resp.body());
+ } catch (const std::exception& ex) {
+ ReportIfwCheckResult(
+ checkable, cmdLine, cr,
+ "Got bad JSON from IfW API on host '" + psHost + "' port '" + psPort + "': " + ex.what(), start, end
+ );
+ return;
+ }
+
+ if (!jsonRoot.IsObjectType()) {
+ ReportIfwCheckResult(
+ checkable, cmdLine, cr,
+ "Got JSON, but not an object, from IfW API on host '"
+ + psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot),
+ start, end
+ );
+ return;
+ }
+
+ Value jsonBranch;
+
+ if (!Dictionary::Ptr(jsonRoot)->Get(psCommand, &jsonBranch)) {
+ ReportIfwCheckResult(
+ checkable, cmdLine, cr,
+ "Missing ." + psCommand + " in JSON object from IfW API on host '"
+ + psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot),
+ start, end
+ );
+ return;
+ }
+
+ if (!jsonBranch.IsObjectType()) {
+ ReportIfwCheckResult(
+ checkable, cmdLine, cr,
+ "." + psCommand + " in JSON from IfW API on host '"
+ + psHost + "' port '" + psPort + "' is not an object: " + JsonEncode(jsonBranch),
+ start, end
+ );
+ return;
+ }
+
+ Dictionary::Ptr result = jsonBranch;
+
+ Value exitcode;
+
+ if (!result->Get("exitcode", &exitcode)) {
+ ReportIfwCheckResult(
+ checkable, cmdLine, cr,
+ "Missing ." + psCommand + ".exitcode in JSON object from IfW API on host '"
+ + psHost + "' port '" + psPort + "': " + JsonEncode(result),
+ start, end
+ );
+ return;
+ }
+
+ static const std::set exitcodes {ServiceOK, ServiceWarning, ServiceCritical, ServiceUnknown};
+ static const auto exitcodeList (Array::FromSet(exitcodes)->Join(", "));
+
+ if (!exitcode.IsNumber() || exitcodes.find(exitcode) == exitcodes.end()) {
+ ReportIfwCheckResult(
+ checkable, cmdLine, cr,
+ "Got bad exitcode " + JsonEncode(exitcode) + " from IfW API on host '" + psHost + "' port '" + psPort
+ + "', expected one of: " + exitcodeList,
+ start, end
+ );
+ return;
+ }
+
+ auto perfdataVal (result->Get("perfdata"));
+ Array::Ptr perfdata;
+
+ try {
+ perfdata = perfdataVal;
+ } catch (const std::exception&) {
+ ReportIfwCheckResult(
+ checkable, cmdLine, cr,
+ "Got bad perfdata " + JsonEncode(perfdataVal) + " from IfW API on host '"
+ + psHost + "' port '" + psPort + "', expected an array",
+ start, end
+ );
+ return;
+ }
+
+ if (perfdata) {
+ ObjectLock oLock (perfdata);
+
+ for (auto& pv : perfdata) {
+ if (!pv.IsString()) {
+ ReportIfwCheckResult(
+ checkable, cmdLine, cr,
+ "Got bad perfdata value " + JsonEncode(perfdata) + " from IfW API on host '"
+ + psHost + "' port '" + psPort + "', expected an array of strings",
+ start, end
+ );
+ return;
+ }
+ }
+ }
+
+ ReportIfwCheckResult(checkable, cmdLine, cr, result->Get("checkresult"), start, end, exitcode, perfdata);
+}
+
+void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros)
+{
+ namespace asio = boost::asio;
+ namespace http = boost::beast::http;
+ using http::field;
+
+ REQUIRE_NOT_NULL(checkable);
+ REQUIRE_NOT_NULL(cr);
+
+ // We're going to just resolve macros for the actual check execution happening elsewhere
+ if (resolvedMacros && !useResolvedMacros) {
+ auto commandEndpoint (checkable->GetCommandEndpoint());
+
+ // There's indeed a command endpoint, obviously for the actual check execution
+ if (commandEndpoint) {
+ // But it doesn't have this function, yet ("ifw-api-check-command")
+ if (!(commandEndpoint->GetCapabilities() & (uint_fast64_t)ApiCapabilities::IfwApiCheckCommand)) {
+ // Assume "ifw-api-check-command" has been imported into a check command which can also work
+ // based on "plugin-check-command", delegate respectively and hope for the best
+ PluginCheckTask::ScriptFunc(checkable, cr, resolvedMacros, useResolvedMacros);
+ return;
+ }
+ }
+ }
+
+ CheckCommand::Ptr command = CheckCommand::ExecuteOverride ? CheckCommand::ExecuteOverride : checkable->GetCheckCommand();
+ auto lcr (checkable->GetLastCheckResult());
+
+ 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);
+
+ auto resolveMacros ([&resolvers, &lcr, &resolvedMacros, useResolvedMacros](const char* macros) -> Value {
+ return MacroProcessor::ResolveMacros(
+ macros, resolvers, lcr, nullptr, MacroProcessor::EscapeCallback(), resolvedMacros, useResolvedMacros
+ );
+ });
+
+ String psCommand = resolveMacros("$ifw_api_command$");
+ Dictionary::Ptr arguments = resolveMacros("$ifw_api_arguments$");
+ String psHost = resolveMacros("$ifw_api_host$");
+ String psPort = resolveMacros("$ifw_api_port$");
+ String expectedSan = resolveMacros("$ifw_api_expected_san$");
+ String cert = resolveMacros("$ifw_api_cert$");
+ String key = resolveMacros("$ifw_api_key$");
+ String ca = resolveMacros("$ifw_api_ca$");
+ String crl = resolveMacros("$ifw_api_crl$");
+ String username = resolveMacros("$ifw_api_username$");
+ String password = resolveMacros("$ifw_api_password$");
+
+ Dictionary::Ptr params = new Dictionary();
+
+ if (arguments) {
+ ObjectLock oLock (arguments);
+ Array::Ptr emptyCmd = new Array();
+
+ for (auto& kv : arguments) {
+ Dictionary::Ptr argSpec;
+
+ if (kv.second.IsObjectType()) {
+ argSpec = Dictionary::Ptr(kv.second)->ShallowClone();
+ } else {
+ argSpec = new Dictionary({{ "value", kv.second }});
+ }
+
+ // See default branch of below switch
+ argSpec->Set("repeat_key", false);
+
+ {
+ ObjectLock oLock (argSpec);
+
+ for (auto& kv : argSpec) {
+ if (kv.second.GetType() == ValueObject) {
+ auto now (Utility::GetTime());
+
+ ReportIfwCheckResult(
+ checkable, command->GetName(), cr,
+ "$ifw_api_arguments$ may not directly contain objects (especially functions).", now, now
+ );
+
+ return;
+ }
+ }
+ }
+
+ /* MacroProcessor::ResolveArguments() converts
+ *
+ * [ "check_example" ]
+ * and
+ * {
+ * "-f" = { set_if = "$example_flag$" }
+ * "-a" = "$example_arg$"
+ * }
+ *
+ * to
+ *
+ * [ "check_example", "-f", "-a", "X" ]
+ *
+ * but we need the args one-by-one like [ "-f" ] or [ "-a", "X" ].
+ */
+ Array::Ptr arg = MacroProcessor::ResolveArguments(
+ emptyCmd, new Dictionary({{kv.first, argSpec}}), resolvers, lcr, resolvedMacros, useResolvedMacros
+ );
+
+ switch (arg ? arg->GetLength() : 0) {
+ case 0:
+ break;
+ case 1: // [ "-f" ]
+ params->Set(arg->Get(0), true);
+ break;
+ case 2: // [ "-a", "X" ]
+ params->Set(arg->Get(0), arg->Get(1));
+ break;
+ default: { // [ "-a", "X", "Y" ]
+ auto k (arg->Get(0));
+
+ arg->Remove(0);
+ params->Set(k, arg);
+ }
+ }
+ }
+ }
+
+ auto checkTimeout (command->GetTimeout());
+ auto checkableTimeout (checkable->GetCheckTimeout());
+
+ if (!checkableTimeout.IsEmpty())
+ checkTimeout = checkableTimeout;
+
+ if (resolvedMacros && !useResolvedMacros)
+ return;
+
+ if (psHost.IsEmpty()) {
+ psHost = "localhost";
+ }
+
+ if (expectedSan.IsEmpty()) {
+ expectedSan = IcingaApplication::GetInstance()->GetNodeName();
+ }
+
+ if (cert.IsEmpty()) {
+ cert = ApiListener::GetDefaultCertPath();
+ }
+
+ if (key.IsEmpty()) {
+ key = ApiListener::GetDefaultKeyPath();
+ }
+
+ if (ca.IsEmpty()) {
+ ca = ApiListener::GetDefaultCaPath();
+ }
+
+ Url::Ptr uri = new Url();
+
+ uri->SetPath({ "v1", "checker" });
+ uri->SetQuery({{ "command", psCommand }});
+
+ static const auto userAgent ("Icinga/" + Application::GetAppVersion());
+ auto relative (uri->Format());
+ auto body (JsonEncode(params));
+ auto req (Shared>::Make());
+
+ req->method(http::verb::post);
+ req->target(relative);
+ req->set(field::accept, "application/json");
+ req->set(field::content_type, "application/json");
+ req->set(field::host, expectedSan + ":" + psPort);
+ req->set(field::user_agent, userAgent);
+ req->body() = body;
+ req->content_length(req->body().size());
+
+ static const auto curlTlsMinVersion ((String("--") + DEFAULT_TLS_PROTOCOLMIN).ToLower());
+
+ Array::Ptr cmdLine = new Array({
+ "curl", "--verbose", curlTlsMinVersion, "--fail-with-body",
+ "--connect-to", expectedSan + ":" + psPort + ":" + psHost + ":" + psPort,
+ "--ciphers", DEFAULT_TLS_CIPHERS,
+ "--cert", cert,
+ "--key", key,
+ "--cacert", ca,
+ "--request", "POST",
+ "--url", "https://" + expectedSan + ":" + psPort + relative,
+ "--user-agent", userAgent,
+ "--header", "Accept: application/json",
+ "--header", "Content-Type: application/json",
+ "--data-raw", body
+ });
+
+ if (!crl.IsEmpty()) {
+ cmdLine->Add("--crlfile");
+ cmdLine->Add(crl);
+ }
+
+ if (!username.IsEmpty() && !password.IsEmpty()) {
+ auto authn (username + ":" + password);
+
+ req->set(field::authorization, "Basic " + Base64::Encode(authn));
+ cmdLine->Add("--user");
+ cmdLine->Add(authn);
+ }
+
+ auto& io (IoEngine::Get().GetIoContext());
+ auto strand (Shared::Make(io));
+ Shared::Ptr ctx;
+ double start = Utility::GetTime();
+
+ try {
+ ctx = SetupSslContext(cert, key, ca, crl, DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo());
+ } catch (const std::exception& ex) {
+ ReportIfwCheckResult(checkable, cmdLine, cr, ex.what(), start, Utility::GetTime());
+ return;
+ }
+
+ auto conn (Shared::Make(io, *ctx, expectedSan));
+
+ IoEngine::SpawnCoroutine(
+ *strand,
+ [strand, checkable, cmdLine, cr, psCommand, psHost, expectedSan, psPort, conn, req, start, checkTimeout](asio::yield_context yc) {
+ Timeout::Ptr timeout = new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(checkTimeout * 1e6)),
+ [&conn, &checkable](boost::asio::yield_context yc) {
+ Log(LogNotice, "IfwApiCheckTask")
+ << "Timeout while checking " << checkable->GetReflectionType()->GetName()
+ << " '" << checkable->GetName() << "', cancelling attempt";
+
+ boost::system::error_code ec;
+ conn->lowest_layer().cancel(ec);
+ }
+ );
+
+ Defer cancelTimeout ([&timeout]() { timeout->Cancel(); });
+
+ DoIfwNetIo(yc, checkable, cmdLine, cr, psCommand, psHost, expectedSan, psPort, *conn, *req, start);
+ }
+ );
+}
diff --git a/lib/methods/ifwapichecktask.hpp b/lib/methods/ifwapichecktask.hpp
new file mode 100644
index 00000000000..39327336b0b
--- /dev/null
+++ b/lib/methods/ifwapichecktask.hpp
@@ -0,0 +1,27 @@
+/* Icinga 2 | (c) 2023 Icinga GmbH | GPLv2+ */
+
+#pragma once
+
+#include "methods/i2-methods.hpp"
+#include "icinga/service.hpp"
+#include "base/dictionary.hpp"
+
+namespace icinga
+{
+
+/**
+ * Executes checks via Icinga for Windows API.
+ *
+ * @ingroup methods
+ */
+class IfwApiCheckTask
+{
+public:
+ static void ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr,
+ const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros);
+
+private:
+ IfwApiCheckTask();
+};
+
+}
diff --git a/lib/methods/methods-itl.conf b/lib/methods/methods-itl.conf
index f9126f7ca24..6249692815a 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 "ifw-api-check-command" use (IfwApiCheck = Internal.IfwApiCheck) {
+ execute = IfwApiCheck
+ }
+
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",
+ "IfwApiCheck",
"ClusterCheck",
"ClusterZoneCheck",
"PluginCheck",
diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp
index cb122aa2146..45f9e7c7728 100644
--- a/lib/remote/apilistener.cpp
+++ b/lib/remote/apilistener.cpp
@@ -605,7 +605,9 @@ static const auto l_AppVersionInt (([]() -> unsigned long {
+ boost::lexical_cast(match[3].str());
})());
-static const auto l_MyCapabilities (ApiCapabilities::ExecuteArbitraryCommand);
+static const auto l_MyCapabilities (
+ (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand | (uint_fast64_t)ApiCapabilities::IfwApiCheckCommand
+);
/**
* Processes a new client connection.
diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp
index 1be29522f85..ffe97a2b324 100644
--- a/lib/remote/apilistener.hpp
+++ b/lib/remote/apilistener.hpp
@@ -67,7 +67,8 @@ struct ConfigDirInformation
*/
enum class ApiCapabilities : uint_fast64_t
{
- ExecuteArbitraryCommand = 1u
+ ExecuteArbitraryCommand = 1u << 0u,
+ IfwApiCheckCommand = 1u << 1u,
};
/**