mirror of
https://github.com/Icinga/icinga2.git
synced 2025-04-08 17:05:25 +02:00
Merge pull request #8064 from WuerthPhoenix/feature/v1-actions-execute-command-8034
WIP implement stub for /v1/actions/execute-command
This commit is contained in:
commit
86f74e2fe5
@ -8,12 +8,16 @@
|
||||
#include "icinga/checkcommand.hpp"
|
||||
#include "icinga/eventcommand.hpp"
|
||||
#include "icinga/notificationcommand.hpp"
|
||||
#include "icinga/clusterevents.hpp"
|
||||
#include "remote/apiaction.hpp"
|
||||
#include "remote/apilistener.hpp"
|
||||
#include "remote/filterutility.hpp"
|
||||
#include "remote/pkiutility.hpp"
|
||||
#include "remote/httputility.hpp"
|
||||
#include "base/utility.hpp"
|
||||
#include "base/convert.hpp"
|
||||
#include "base/defer.hpp"
|
||||
#include "remote/actionshandler.hpp"
|
||||
#include <fstream>
|
||||
|
||||
using namespace icinga;
|
||||
@ -552,8 +556,245 @@ Dictionary::Ptr ApiActions::GenerateTicket(const ConfigObject::Ptr&,
|
||||
+ cn + "'.", additional);
|
||||
}
|
||||
|
||||
Value ApiActions::GetSingleObjectByNameUsingPermissions(const String& type, const String& objectName, const ApiUser::Ptr& user)
|
||||
{
|
||||
Dictionary::Ptr queryParams = new Dictionary();
|
||||
queryParams->Set("type", type);
|
||||
queryParams->Set(type.ToLower(), objectName);
|
||||
|
||||
QueryDescription qd;
|
||||
qd.Types.insert(type);
|
||||
qd.Permission = "objects/query/" + type;
|
||||
|
||||
std::vector<Value> objs;
|
||||
try {
|
||||
objs = FilterUtility::GetFilterTargets(qd, queryParams, user);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogWarning, "ApiActions") << DiagnosticInformation(ex);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (objs.empty())
|
||||
return nullptr;
|
||||
|
||||
return objs.at(0);
|
||||
};
|
||||
|
||||
Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object,
|
||||
const Dictionary::Ptr& params)
|
||||
{
|
||||
return ApiActions::CreateResult(501, "Not implemented");
|
||||
ApiListener::Ptr listener = ApiListener::GetInstance();
|
||||
if (!listener)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("No ApiListener instance configured."));
|
||||
|
||||
/* Get command_type */
|
||||
String command_type = "EventCommand";
|
||||
if (params->Contains("command_type"))
|
||||
command_type = HttpUtility::GetLastParameter(params, "command_type");
|
||||
|
||||
/* Validate command_type */
|
||||
if (command_type != "EventCommand" && command_type != "CheckCommand" && command_type != "NotificationCommand")
|
||||
return ApiActions::CreateResult(400, "Invalid command_type '" + command_type + "'.");
|
||||
|
||||
Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
|
||||
if (!checkable)
|
||||
return ApiActions::CreateResult(404, "Can't start a command execution for a non-existent object.");
|
||||
|
||||
/* Get TTL param */
|
||||
if (!params->Contains("ttl"))
|
||||
return ApiActions::CreateResult(400, "Parameter ttl is required.");
|
||||
|
||||
double ttl = HttpUtility::GetLastParameter(params, "ttl");
|
||||
if (ttl <= 0)
|
||||
return ApiActions::CreateResult(400, "Parameter ttl must be greater than 0.");
|
||||
|
||||
ObjectLock oLock (checkable);
|
||||
|
||||
Host::Ptr host;
|
||||
Service::Ptr service;
|
||||
tie(host, service) = GetHostService(checkable);
|
||||
|
||||
String endpoint = "$command_endpoint$";
|
||||
if (params->Contains("endpoint"))
|
||||
endpoint = HttpUtility::GetLastParameter(params, "endpoint");
|
||||
|
||||
MacroProcessor::ResolverList resolvers;
|
||||
if (params->Contains("macros")) {
|
||||
Value macros = HttpUtility::GetLastParameter(params, "macros");
|
||||
if (macros.IsObjectType<Dictionary>()) {
|
||||
resolvers.emplace_back("override", macros);
|
||||
}
|
||||
else
|
||||
return ApiActions::CreateResult(400, "Parameter macros must be a dictionary.");
|
||||
}
|
||||
|
||||
if (service)
|
||||
resolvers.emplace_back("service", service);
|
||||
resolvers.emplace_back("host", host);
|
||||
resolvers.emplace_back("icinga", IcingaApplication::GetInstance());
|
||||
|
||||
String resolved_endpoint = MacroProcessor::ResolveMacros(
|
||||
endpoint, resolvers, checkable->GetLastCheckResult(),
|
||||
nullptr, MacroProcessor::EscapeCallback(), nullptr, false
|
||||
);
|
||||
|
||||
if (!ActionsHandler::AuthenticatedApiUser)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Can't find API user."));
|
||||
|
||||
/* Get endpoint */
|
||||
Endpoint::Ptr endpointPtr = GetSingleObjectByNameUsingPermissions(Endpoint::GetTypeName(), resolved_endpoint, ActionsHandler::AuthenticatedApiUser);
|
||||
if (!endpointPtr)
|
||||
return ApiActions::CreateResult(404, "Can't find a valid endpoint for '" + resolved_endpoint + "'.");
|
||||
|
||||
/* Get command */
|
||||
String command;
|
||||
if (!params->Contains("command")) {
|
||||
if (command_type == "CheckCommand" ) {
|
||||
command = "$check_command$";
|
||||
} else if (command_type == "EventCommand") {
|
||||
command = "$event_command$";
|
||||
} else if (command_type == "NotificationCommand") {
|
||||
command = "$notification_command$";
|
||||
}
|
||||
} else {
|
||||
command = HttpUtility::GetLastParameter(params, "command");
|
||||
}
|
||||
|
||||
/* Resolve command macro */
|
||||
String resolved_command = MacroProcessor::ResolveMacros(
|
||||
command, resolvers, checkable->GetLastCheckResult(), nullptr,
|
||||
MacroProcessor::EscapeCallback(), nullptr, false
|
||||
);
|
||||
|
||||
CheckResult::Ptr cr = checkable->GetLastCheckResult();
|
||||
|
||||
/* Check if resolved_command exists and it is of type command_type */
|
||||
Dictionary::Ptr execMacros = new Dictionary();
|
||||
|
||||
MacroResolver::OverrideMacros = execMacros;
|
||||
Defer o ([]() {
|
||||
MacroResolver::OverrideMacros = nullptr;
|
||||
});
|
||||
|
||||
if (command_type == "CheckCommand") {
|
||||
CheckCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(CheckCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
|
||||
if (!cmd)
|
||||
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
|
||||
else
|
||||
cmd->Execute(checkable, cr, execMacros, false);
|
||||
} else if (command_type == "EventCommand") {
|
||||
EventCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(EventCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
|
||||
if (!cmd)
|
||||
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
|
||||
else
|
||||
cmd->Execute(checkable, execMacros, false);
|
||||
} else if (command_type == "NotificationCommand") {
|
||||
NotificationCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(NotificationCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
|
||||
if (!cmd)
|
||||
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
|
||||
else {
|
||||
/* Get user */
|
||||
String user_string = "";
|
||||
if (params->Contains("user"))
|
||||
user_string = HttpUtility::GetLastParameter(params, "user");
|
||||
|
||||
/* Resolve user macro */
|
||||
String resolved_user = MacroProcessor::ResolveMacros(
|
||||
user_string, resolvers, checkable->GetLastCheckResult(), nullptr,
|
||||
MacroProcessor::EscapeCallback(), nullptr, false
|
||||
);
|
||||
|
||||
User::Ptr user = GetSingleObjectByNameUsingPermissions(User::GetTypeName(), resolved_user, ActionsHandler::AuthenticatedApiUser);
|
||||
if (!user)
|
||||
return ApiActions::CreateResult(404, "Can't find a valid user for '" + resolved_user + "'.");
|
||||
|
||||
/* Get notification */
|
||||
String notification_string = "";
|
||||
if (params->Contains("notification"))
|
||||
notification_string = HttpUtility::GetLastParameter(params, "notification");
|
||||
|
||||
/* Resolve notification macro */
|
||||
String resolved_notification = MacroProcessor::ResolveMacros(
|
||||
notification_string, resolvers, checkable->GetLastCheckResult(), nullptr,
|
||||
MacroProcessor::EscapeCallback(), nullptr, false
|
||||
);
|
||||
|
||||
Notification::Ptr notification = GetSingleObjectByNameUsingPermissions(Notification::GetTypeName(), resolved_notification, ActionsHandler::AuthenticatedApiUser);
|
||||
if (!notification)
|
||||
return ApiActions::CreateResult(404, "Can't find a valid notification for '" + resolved_notification + "'.");
|
||||
|
||||
cmd->Execute(notification, user, cr, NotificationType::NotificationCustom,
|
||||
ActionsHandler::AuthenticatedApiUser->GetName(), "", execMacros, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* This generates a UUID */
|
||||
String uuid = Utility::NewUniqueID();
|
||||
|
||||
/* Create the deadline */
|
||||
double deadline = Utility::GetTime() + ttl;
|
||||
|
||||
/* Update executions */
|
||||
Dictionary::Ptr pending_execution = new Dictionary();
|
||||
pending_execution->Set("pending", true);
|
||||
pending_execution->Set("deadline", deadline);
|
||||
Dictionary::Ptr executions = checkable->GetExecutions();
|
||||
if (!executions)
|
||||
executions = new Dictionary();
|
||||
executions->Set(uuid, pending_execution);
|
||||
checkable->SetExecutions(executions);
|
||||
|
||||
/* Broadcast the update */
|
||||
Dictionary::Ptr executionsToBroadcast = new Dictionary();
|
||||
executionsToBroadcast->Set(uuid, pending_execution);
|
||||
Dictionary::Ptr updateParams = new Dictionary();
|
||||
updateParams->Set("host", host->GetName());
|
||||
if (service)
|
||||
updateParams->Set("service", service->GetShortName());
|
||||
updateParams->Set("executions", executionsToBroadcast);
|
||||
|
||||
Dictionary::Ptr updateMessage = new Dictionary();
|
||||
updateMessage->Set("jsonrpc", "2.0");
|
||||
updateMessage->Set("method", "event::UpdateExecutions");
|
||||
updateMessage->Set("params", updateParams);
|
||||
|
||||
MessageOrigin::Ptr origin = new MessageOrigin();
|
||||
listener->RelayMessage(origin, checkable, updateMessage, true);
|
||||
|
||||
/* Create execution parameters */
|
||||
Dictionary::Ptr execParams = new Dictionary();
|
||||
execParams->Set("command_type", command_type);
|
||||
execParams->Set("command", resolved_command);
|
||||
execParams->Set("host", host->GetName());
|
||||
if (service)
|
||||
execParams->Set("service", service->GetShortName());
|
||||
|
||||
/*
|
||||
* If the host/service object specifies the 'check_timeout' attribute,
|
||||
* forward this to the remote endpoint to limit the command execution time.
|
||||
*/
|
||||
if (!checkable->GetCheckTimeout().IsEmpty())
|
||||
execParams->Set("check_timeout", checkable->GetCheckTimeout());
|
||||
|
||||
execParams->Set("source", uuid);
|
||||
execParams->Set("deadline", deadline);
|
||||
execParams->Set("macros", execMacros);
|
||||
|
||||
/* Execute command */
|
||||
bool local = endpointPtr == Endpoint::GetLocalEndpoint();
|
||||
if (local) {
|
||||
ClusterEvents::ExecuteCommandAPIHandler(origin, execParams);
|
||||
} else {
|
||||
Dictionary::Ptr execMessage = new Dictionary();
|
||||
execMessage->Set("jsonrpc", "2.0");
|
||||
execMessage->Set("method", "event::ExecuteCommand");
|
||||
execMessage->Set("params", execParams);
|
||||
|
||||
listener->SyncSendMessage(endpointPtr, execMessage);
|
||||
}
|
||||
|
||||
Dictionary::Ptr result = new Dictionary();
|
||||
result->Set("checkable", checkable->GetName());
|
||||
result->Set("execution", uuid);
|
||||
return ApiActions::CreateResult(202, "Accepted", result);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "icinga/i2-icinga.hpp"
|
||||
#include "base/configobject.hpp"
|
||||
#include "base/dictionary.hpp"
|
||||
#include "remote/apiuser.hpp"
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
@ -33,6 +34,7 @@ public:
|
||||
|
||||
private:
|
||||
static Dictionary::Ptr CreateResult(int code, const String& status, const Dictionary::Ptr& additional = nullptr);
|
||||
static Value GetSingleObjectByNameUsingPermissions(const String& type, const String& value, const ApiUser::Ptr& user);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -77,7 +77,12 @@ void ClusterEvents::EnqueueCheck(const MessageOrigin::Ptr& origin, const Diction
|
||||
|
||||
void ClusterEvents::ExecuteCheckFromQueue(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) {
|
||||
|
||||
Endpoint::Ptr sourceEndpoint = origin->FromClient->GetEndpoint();
|
||||
Endpoint::Ptr sourceEndpoint;
|
||||
if (origin->FromClient) {
|
||||
sourceEndpoint = origin->FromClient->GetEndpoint();
|
||||
} else if (origin->IsLocal()){
|
||||
sourceEndpoint = Endpoint::GetLocalEndpoint();
|
||||
}
|
||||
|
||||
if (!sourceEndpoint || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone))) {
|
||||
Log(LogNotice, "ClusterEvents")
|
||||
|
@ -949,5 +949,44 @@ Value ClusterEvents::ExecutedCommandAPIHandler(const MessageOrigin::Ptr& origin,
|
||||
|
||||
Value ClusterEvents::UpdateExecutionsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
|
||||
{
|
||||
return "Not implemented";
|
||||
Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
|
||||
if (!endpoint) {
|
||||
Log(LogNotice, "ClusterEvents")
|
||||
<< "Discarding 'update executions API handler' message from '" << origin->FromClient->GetIdentity()
|
||||
<< "': Invalid endpoint origin (client not allowed).";
|
||||
|
||||
return Empty;
|
||||
}
|
||||
|
||||
Host::Ptr host = Host::GetByName(params->Get("host"));
|
||||
if (!host)
|
||||
return Empty;
|
||||
|
||||
Checkable::Ptr checkable;
|
||||
|
||||
if (params->Contains("service"))
|
||||
checkable = host->GetServiceByShortName(params->Get("service"));
|
||||
else
|
||||
checkable = host;
|
||||
|
||||
if (!checkable)
|
||||
return Empty;
|
||||
|
||||
ObjectLock oLock (checkable);
|
||||
|
||||
if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
|
||||
Log(LogNotice, "ClusterEvents")
|
||||
<< "Discarding 'update executions API handler' message for checkable '" << checkable->GetName()
|
||||
<< "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
|
||||
return Empty;
|
||||
}
|
||||
|
||||
Dictionary::Ptr executions = checkable->GetExecutions();
|
||||
if (!executions)
|
||||
executions = new Dictionary();
|
||||
Dictionary::Ptr newExecutions = params->Get("executions");
|
||||
newExecutions->CopyTo(executions);
|
||||
checkable->SetExecutions(executions);
|
||||
|
||||
return Empty;
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
thread_local Dictionary::Ptr MacroResolver::OverrideMacros;
|
||||
|
||||
Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
|
||||
const CheckResult::Ptr& cr, String *missingMacro,
|
||||
const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,
|
||||
|
@ -21,6 +21,8 @@ class MacroResolver
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(MacroResolver);
|
||||
|
||||
static thread_local Dictionary::Ptr OverrideMacros;
|
||||
|
||||
virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const = 0;
|
||||
};
|
||||
|
||||
|
@ -29,6 +29,9 @@ void PluginCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
|
||||
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);
|
||||
|
@ -4,12 +4,15 @@
|
||||
#include "remote/httputility.hpp"
|
||||
#include "remote/filterutility.hpp"
|
||||
#include "remote/apiaction.hpp"
|
||||
#include "base/defer.hpp"
|
||||
#include "base/exception.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include <set>
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
thread_local ApiUser::Ptr ActionsHandler::AuthenticatedApiUser;
|
||||
|
||||
REGISTER_URLHANDLER("/v1/actions", ActionsHandler);
|
||||
|
||||
bool ActionsHandler::HandleRequest(
|
||||
@ -71,6 +74,11 @@ bool ActionsHandler::HandleRequest(
|
||||
|
||||
bool verbose = false;
|
||||
|
||||
ActionsHandler::AuthenticatedApiUser = user;
|
||||
Defer a ([]() {
|
||||
ActionsHandler::AuthenticatedApiUser = nullptr;
|
||||
});
|
||||
|
||||
if (params)
|
||||
verbose = HttpUtility::GetLastParameter(params, "verbose");
|
||||
|
||||
@ -94,7 +102,7 @@ bool ActionsHandler::HandleRequest(
|
||||
int statusCode = 500;
|
||||
|
||||
for (const Dictionary::Ptr& res : results) {
|
||||
if (res->Contains("code") && res->Get("code") == 200) {
|
||||
if (res->Contains("code") && res->Get("code") >= 200 && res->Get("code") <= 299) {
|
||||
statusCode = 200;
|
||||
break;
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ class ActionsHandler final : public HttpHandler
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(ActionsHandler);
|
||||
|
||||
static thread_local ApiUser::Ptr AuthenticatedApiUser;
|
||||
|
||||
bool HandleRequest(
|
||||
AsioTlsStream& stream,
|
||||
const ApiUser::Ptr& user,
|
||||
|
Loading…
x
Reference in New Issue
Block a user