diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md
index 8ee96aa86b..39a434f388 100644
--- a/doc/12-icinga2-api.md
+++ b/doc/12-icinga2-api.md
@@ -154,6 +154,30 @@ was malformed.
A status in the range of 500 generally means that there was a server-side problem
and Icinga 2 is unable to process your request.
+Starting with Icinga 2 v2.16.0 we introduced HTTP chunked transfer encoding for some of the API endpoints.
+As a consequence, the `/v1/actions/` and `/v1/objects/` endpoints will always return a `202 Accepted` status code as a
+general HTTP status, but the status of each individual operation remains the same as before, and will continue to report
+`2xx`, `4xx` or `5xx` status codes. For example, you may see such a response when trying to delete multiple objects.
+
+```json
+{
+ "results": [
+ {
+ "code": 200,
+ "status": "Object was deleted."
+ },
+ {
+ "code": 500,
+ "status": "Object could not be deleted.",
+ }
+ ]
+}
+```
+
+In such cases, you should always check the individual result entries for their status code and not rely solely on the
+overall HTTP status code. There are also a number of other endpoints which use chunked transfer encoding, but have no
+behavioral difference in terms of the overall HTTP status code and will continue to return `200` for successful requests.
+
### Security
* HTTPS only.
@@ -2742,7 +2766,7 @@ r = requests.post(request_url,
print "Request URL: " + str(r.url)
print "Status code: " + str(r.status_code)
-if (r.status_code == 200):
+if (r.status_code == 202):
print "Result: " + json.dumps(r.json())
else:
print r.text
@@ -2791,7 +2815,7 @@ rescue => e
end
puts "Status: " + response.code.to_s
-if response.code == 200
+if response.code == 202
puts "Result: " + (JSON.pretty_generate JSON.parse(response.body))
else
puts "Error: " + response
@@ -2846,7 +2870,7 @@ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
print "Status: " . $code . "\n";
-if ($code == 200) {
+if ($code == 202) {
$response = json_decode($response, true);
print_r($response);
}
@@ -2898,7 +2922,7 @@ $client->POST("/v1/objects/services", $data);
my $status = $client->responseCode();
print "Status: " . $status . "\n";
my $response = $client->responseContent();
-if ($status == 200) {
+if ($status == 202) {
print "Result: " . Dumper(decode_json($response)) . "\n";
} else {
print "Error: " . $response . "\n";
@@ -2921,14 +2945,13 @@ import (
"bytes"
"crypto/tls"
"log"
- "io/ioutil"
"net/http"
)
func main() {
- var urlBase= "https://localhost:5665"
- var apiUser= "root"
- var apiPass= "icinga"
+ var urlBase = "https://localhost:5665"
+ var apiUser = "root"
+ var apiPass = "icinga"
urlEndpoint := urlBase + "/v1/objects/services"
@@ -2943,7 +2966,7 @@ func main() {
"filter": "match(\"ping*\", service.name)"
}`)
- req, err := http.NewRequest("POST", urlEndpoint, bytes.NewBuffer(requestBody))
+ req, err := http.NewRequest("POST", urlEndpoint, bytes.NewReader(requestBody))
req.Header.Set("Accept", "application/json")
req.Header.Set("X-HTTP-Method-Override", "GET")
@@ -2958,13 +2981,16 @@ func main() {
log.Print("Response status:", resp.Status)
- bodyBytes, _ := ioutil.ReadAll(resp.Body)
- bodyString := string(bodyBytes)
+ buf := new(bytes.Buffer)
+ if _, err := buf.ReadFrom(resp.Body); err != nil {
+ log.Fatal("Read error:", err)
+ return
+ }
- if resp.StatusCode == http.StatusOK {
- log.Print("Result: " + bodyString)
+ if resp.StatusCode == http.StatusAccepted {
+ log.Print("Result: " + buf.String())
} else {
- log.Fatal(bodyString)
+ log.Fatal(buf.String())
}
}
```
@@ -2972,8 +2998,7 @@ func main() {
Build the binary:
```bash
-go build icinga.go
-./icinga
+go run ./icinga.go
```
#### Example API Client in Powershell
diff --git a/lib/base/generator.hpp b/lib/base/generator.hpp
index fa41e67fc1..2a633ee206 100644
--- a/lib/base/generator.hpp
+++ b/lib/base/generator.hpp
@@ -10,39 +10,86 @@ namespace icinga
{
/**
- * ValueGenerator is a class that defines a generator function type for producing Values on demand.
+ * Abstract base class for generators that produce a sequence of Values.
*
- * This class is used to create generator functions that can yield any values that can be represented by the
- * Icinga Value type. The generator function is exhausted when it returns `std::nullopt`, indicating that there
- * are no more values to produce. Subsequent calls to `Next()` will always return `std::nullopt` after exhaustion.
+ * @note Any instance of a Generator should be treated as an @c Array -like object that produces its elements
+ * on-the-fly.
*
* @ingroup base
*/
-class ValueGenerator final : public Object
+class Generator : public Object
{
public:
- DECLARE_PTR_TYPEDEFS(ValueGenerator);
+ DECLARE_PTR_TYPEDEFS(Generator);
/**
- * Generates a Value using the provided generator function.
+ * Produces the next Value in the sequence.
+ *
+ * This method returns the next Value produced by the generator. If the generator is exhausted,
+ * it returns std::nullopt for all subsequent calls to this method.
*
- * The generator function should return an `std::optional` which contains the produced Value or
- * `std::nullopt` when there are no more values to produce. After the generator function returns `std::nullopt`,
- * the generator is considered exhausted, and further calls to `Next()` will always return `std::nullopt`.
+ * @return The next Value in the sequence, or std::nullopt if the generator is exhausted.
*/
- using GenFunc = std::function()>;
+ virtual std::optional Next() = 0;
+};
- explicit ValueGenerator(GenFunc generator): m_Generator(std::move(generator))
+/**
+ * A generator that transforms elements of a container into Values using a provided transformation function.
+ *
+ * This class takes a container and a transformation function as input. It uses the transformation function
+ * to convert each element of the container into a Value. The generator produces Values on-the-fly as they
+ * are requested via the `Next()` method. If the transformation function returns `std::nullopt` for an element,
+ * even though there are more elements in the container, the generator will yield that `std::nullopt` value like
+ * any other Value and continue to the next element on subsequent calls to @c Next().
+ *
+ * @tparam Container The type of the container holding the elements to be transformed.
+ *
+ * @ingroup base
+ */
+template
+class ValueGenerator final : public Generator
+{
+public:
+ DECLARE_PTR_TYPEDEFS(ValueGenerator);
+
+ // The type of elements in the container and the type to be passed to the transformation function.
+ using ValueType = typename Container::value_type;
+
+ // The type of the transformation function that takes a ValueType and returns an optional Value.
+ using TransFormFunc = std::function (const ValueType&)>;
+
+ /**
+ * Constructs a ValueGenerator with the given container and transformation function.
+ *
+ * The generator will iterate over the elements of the container, applying the transformation function
+ * to each element to produce values on-the-fly. You must ensure that the container remains valid for
+ * the lifetime of the generator.
+ *
+ * @param container The container holding the elements to be transformed.
+ * @param generator The transformation function to convert elements into Values.
+ */
+ ValueGenerator(Container& container, TransFormFunc generator)
+ : m_It{container.begin()}, m_End{container.end()}, m_Func{std::move(generator)}
{
}
- std::optional Next() const
+ std::optional Next() override
{
- return m_Generator();
+ if (m_It == m_End) {
+ return std::nullopt;
+ }
+
+ auto next = m_Func(*m_It);
+ ++m_It;
+ return next;
}
private:
- GenFunc m_Generator; // The generator function that produces Values.
+ using Iterator = typename Container::iterator;
+ Iterator m_It; // Current iterator position.
+ Iterator m_End; // End iterator position.
+
+ TransFormFunc m_Func; // The transformation function.
};
-}
+} // namespace icinga
diff --git a/lib/base/json.cpp b/lib/base/json.cpp
index 531ab45b22..eed6679a5a 100644
--- a/lib/base/json.cpp
+++ b/lib/base/json.cpp
@@ -73,7 +73,7 @@ void JsonEncoder::Encode(const Value& value, boost::asio::yield_context* yc)
EncodeObject(static_pointer_cast(obj), extractor, yc);
} else if (type == Array::TypeInstance) {
EncodeArray(static_pointer_cast(obj), yc);
- } else if (auto gen(dynamic_pointer_cast(obj)); gen) {
+ } else if (auto gen(dynamic_pointer_cast(obj)); gen) {
EncodeValueGenerator(gen, yc);
} else {
// Some other non-serializable object type!
@@ -126,7 +126,7 @@ void JsonEncoder::EncodeArray(const Array::Ptr& array, boost::asio::yield_contex
* @param yc The optional yield context for asynchronous operations. If provided, it allows the encoder
* to flush the output stream safely when it has not acquired any object lock on the parent containers.
*/
-void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc)
+void JsonEncoder::EncodeValueGenerator(const Generator::Ptr& generator, boost::asio::yield_context* yc)
{
BeginContainer('[');
bool isEmpty = true;
diff --git a/lib/base/json.hpp b/lib/base/json.hpp
index a5ed462807..95bb6d80c2 100644
--- a/lib/base/json.hpp
+++ b/lib/base/json.hpp
@@ -73,7 +73,7 @@ class JsonEncoder
private:
void EncodeArray(const Array::Ptr& array, boost::asio::yield_context* yc);
- void EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc);
+ void EncodeValueGenerator(const Generator::Ptr& generator, boost::asio::yield_context* yc);
template
void EncodeObject(const Iterable& container, const ValExtractor& extractor, boost::asio::yield_context* yc);
diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp
index e7c2e35109..f6cb229c57 100644
--- a/lib/icinga/apiactions.cpp
+++ b/lib/icinga/apiactions.cpp
@@ -53,8 +53,11 @@ Dictionary::Ptr ApiActions::CreateResult(int code, const String& status,
return result;
}
-Dictionary::Ptr ApiActions::ProcessCheckResult(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::ProcessCheckResult(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
using Result = Checkable::ProcessingResult;
@@ -140,8 +143,11 @@ Dictionary::Ptr ApiActions::ProcessCheckResult(const ConfigObject::Ptr& object,
return ApiActions::CreateResult(500, "Unexpected result (" + std::to_string(static_cast(result)) + ") for object '" + checkable->GetName() + "'. Please submit a bug report at https://github.com/Icinga/icinga2");
}
-Dictionary::Ptr ApiActions::RescheduleCheck(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::RescheduleCheck(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
Checkable::Ptr checkable = static_pointer_cast(object);
@@ -165,8 +171,11 @@ Dictionary::Ptr ApiActions::RescheduleCheck(const ConfigObject::Ptr& object,
return ApiActions::CreateResult(200, "Successfully rescheduled check for object '" + checkable->GetName() + "'.");
}
-Dictionary::Ptr ApiActions::SendCustomNotification(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::SendCustomNotification(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
Checkable::Ptr checkable = static_pointer_cast(object);
@@ -188,8 +197,11 @@ Dictionary::Ptr ApiActions::SendCustomNotification(const ConfigObject::Ptr& obje
return ApiActions::CreateResult(200, "Successfully sent custom notification for object '" + checkable->GetName() + "'.");
}
-Dictionary::Ptr ApiActions::DelayNotification(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::DelayNotification(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
Checkable::Ptr checkable = static_pointer_cast(object);
@@ -206,8 +218,11 @@ Dictionary::Ptr ApiActions::DelayNotification(const ConfigObject::Ptr& object,
return ApiActions::CreateResult(200, "Successfully delayed notifications for object '" + checkable->GetName() + "'.");
}
-Dictionary::Ptr ApiActions::AcknowledgeProblem(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::AcknowledgeProblem(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
Checkable::Ptr checkable = static_pointer_cast(object);
@@ -268,8 +283,11 @@ Dictionary::Ptr ApiActions::AcknowledgeProblem(const ConfigObject::Ptr& object,
return ApiActions::CreateResult(200, "Successfully acknowledged problem for object '" + checkable->GetName() + "'.");
}
-Dictionary::Ptr ApiActions::RemoveAcknowledgement(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::RemoveAcknowledgement(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
Checkable::Ptr checkable = static_pointer_cast(object);
@@ -292,8 +310,11 @@ Dictionary::Ptr ApiActions::RemoveAcknowledgement(const ConfigObject::Ptr& objec
return ApiActions::CreateResult(200, "Successfully removed acknowledgement for object '" + checkable->GetName() + "'.");
}
-Dictionary::Ptr ApiActions::AddComment(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::AddComment(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
Checkable::Ptr checkable = static_pointer_cast(object);
@@ -331,8 +352,11 @@ Dictionary::Ptr ApiActions::AddComment(const ConfigObject::Ptr& object,
+ "'.", additional);
}
-Dictionary::Ptr ApiActions::RemoveComment(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::RemoveComment(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
ConfigObjectsSharedLock lock (std::try_to_lock);
@@ -365,8 +389,11 @@ Dictionary::Ptr ApiActions::RemoveComment(const ConfigObject::Ptr& object,
return ApiActions::CreateResult(200, "Successfully removed comment '" + commentName + "'.");
}
-Dictionary::Ptr ApiActions::ScheduleDowntime(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::ScheduleDowntime(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
Checkable::Ptr checkable = static_pointer_cast(object);
@@ -535,8 +562,11 @@ Dictionary::Ptr ApiActions::ScheduleDowntime(const ConfigObject::Ptr& object,
downtimeName + "' for object '" + checkable->GetName() + "'.", additional);
}
-Dictionary::Ptr ApiActions::RemoveDowntime(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::RemoveDowntime(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr&,
+ const Dictionary::Ptr& params
+)
{
ConfigObjectsSharedLock lock (std::try_to_lock);
@@ -588,24 +618,21 @@ Dictionary::Ptr ApiActions::RemoveDowntime(const ConfigObject::Ptr& object,
}
}
-Dictionary::Ptr ApiActions::ShutdownProcess(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::ShutdownProcess(const ConfigObject::Ptr&, const ApiUser::Ptr&, const Dictionary::Ptr&)
{
Application::RequestShutdown();
return ApiActions::CreateResult(200, "Shutting down Icinga 2.");
}
-Dictionary::Ptr ApiActions::RestartProcess(const ConfigObject::Ptr& object,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::RestartProcess(const ConfigObject::Ptr&, const ApiUser::Ptr&, const Dictionary::Ptr&)
{
Application::RequestRestart();
return ApiActions::CreateResult(200, "Restarting Icinga 2.");
}
-Dictionary::Ptr ApiActions::GenerateTicket(const ConfigObject::Ptr&,
- const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::GenerateTicket(const ConfigObject::Ptr&, const ApiUser::Ptr&, const Dictionary::Ptr& params)
{
if (!params->Contains("cn"))
return ApiActions::CreateResult(400, "Option 'cn' is required");
@@ -653,7 +680,11 @@ Value ApiActions::GetSingleObjectByNameUsingPermissions(const String& type, cons
return objs.at(0);
};
-Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, const Dictionary::Ptr& params)
+Dictionary::Ptr ApiActions::ExecuteCommand(
+ const ConfigObject::Ptr& object,
+ const ApiUser::Ptr& apiUser,
+ const Dictionary::Ptr& params
+)
{
ApiListener::Ptr listener = ApiListener::GetInstance();
@@ -717,11 +748,11 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons
nullptr, MacroProcessor::EscapeCallback(), nullptr, false
);
- if (!ActionsHandler::AuthenticatedApiUser)
+ if (!apiUser)
BOOST_THROW_EXCEPTION(std::invalid_argument("Can't find API user."));
/* Get endpoint */
- Endpoint::Ptr endpointPtr = GetSingleObjectByNameUsingPermissions(Endpoint::GetTypeName(), resolved_endpoint, ActionsHandler::AuthenticatedApiUser);
+ Endpoint::Ptr endpointPtr = GetSingleObjectByNameUsingPermissions(Endpoint::GetTypeName(), resolved_endpoint, apiUser);
if (!endpointPtr)
return ApiActions::CreateResult(404, "Can't find a valid endpoint for '" + resolved_endpoint + "'.");
@@ -779,7 +810,7 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons
Dictionary::Ptr execParams = new Dictionary();
if (command_type == "CheckCommand") {
- CheckCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(CheckCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
+ CheckCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(CheckCommand::GetTypeName(), resolved_command, apiUser);
if (!cmd)
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
@@ -791,7 +822,7 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons
cmd->Execute(checkable, cr, listener->GetWaitGroup(), execMacros, false);
}
} else if (command_type == "EventCommand") {
- EventCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(EventCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
+ EventCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(EventCommand::GetTypeName(), resolved_command, apiUser);
if (!cmd)
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
@@ -803,7 +834,7 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons
cmd->Execute(checkable, execMacros, false);
}
} else if (command_type == "NotificationCommand") {
- NotificationCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(NotificationCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
+ NotificationCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(NotificationCommand::GetTypeName(), resolved_command, apiUser);
if (!cmd)
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
@@ -820,7 +851,7 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons
MacroProcessor::EscapeCallback(), nullptr, false
);
- User::Ptr user = GetSingleObjectByNameUsingPermissions(User::GetTypeName(), resolved_user, ActionsHandler::AuthenticatedApiUser);
+ User::Ptr user = GetSingleObjectByNameUsingPermissions(User::GetTypeName(), resolved_user, apiUser);
if (!user)
return ApiActions::CreateResult(404, "Can't find a valid user for '" + resolved_user + "'.");
@@ -839,7 +870,7 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons
MacroProcessor::EscapeCallback(), nullptr, false
);
- Notification::Ptr notification = GetSingleObjectByNameUsingPermissions(Notification::GetTypeName(), resolved_notification, ActionsHandler::AuthenticatedApiUser);
+ Notification::Ptr notification = GetSingleObjectByNameUsingPermissions(Notification::GetTypeName(), resolved_notification, apiUser);
if (!notification)
return ApiActions::CreateResult(404, "Can't find a valid notification for '" + resolved_notification + "'.");
@@ -852,7 +883,7 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons
});
cmd->Execute(notification, user, cr, NotificationType::NotificationCustom,
- ActionsHandler::AuthenticatedApiUser->GetName(), "", execMacros, false);
+ apiUser->GetName(), "", execMacros, false);
}
}
diff --git a/lib/icinga/apiactions.hpp b/lib/icinga/apiactions.hpp
index b6ba835008..cc5bafe834 100644
--- a/lib/icinga/apiactions.hpp
+++ b/lib/icinga/apiactions.hpp
@@ -17,20 +17,20 @@ namespace icinga
class ApiActions
{
public:
- static Dictionary::Ptr ProcessCheckResult(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr RescheduleCheck(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr SendCustomNotification(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr DelayNotification(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr AcknowledgeProblem(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr RemoveAcknowledgement(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr AddComment(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr RemoveComment(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr ScheduleDowntime(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr RemoveDowntime(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr ShutdownProcess(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr RestartProcess(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr GenerateTicket(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
- static Dictionary::Ptr ExecuteCommand(const ConfigObject::Ptr& object, const Dictionary::Ptr& params);
+ static Dictionary::Ptr ProcessCheckResult(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RescheduleCheck(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr SendCustomNotification(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr DelayNotification(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr AcknowledgeProblem(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RemoveAcknowledgement(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr AddComment(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RemoveComment(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr ScheduleDowntime(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr RemoveDowntime(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr ShutdownProcess(const ConfigObject::Ptr&, const ApiUser::Ptr&, const Dictionary::Ptr&);
+ static Dictionary::Ptr RestartProcess(const ConfigObject::Ptr&, const ApiUser::Ptr&, const Dictionary::Ptr&);
+ static Dictionary::Ptr GenerateTicket(const ConfigObject::Ptr& object, const ApiUser::Ptr&, const Dictionary::Ptr& params);
+ static Dictionary::Ptr ExecuteCommand(const ConfigObject::Ptr& object, const ApiUser::Ptr& apiUser, const Dictionary::Ptr& params);
private:
static Dictionary::Ptr CreateResult(int code, const String& status, const Dictionary::Ptr& additional = nullptr);
diff --git a/lib/remote/actionshandler.cpp b/lib/remote/actionshandler.cpp
index b9853945be..889b487b68 100644
--- a/lib/remote/actionshandler.cpp
+++ b/lib/remote/actionshandler.cpp
@@ -6,13 +6,13 @@
#include "remote/apiaction.hpp"
#include "base/defer.hpp"
#include "base/exception.hpp"
+#include "base/generator.hpp"
#include "base/logger.hpp"
+#include
#include
using namespace icinga;
-thread_local ApiUser::Ptr ActionsHandler::AuthenticatedApiUser;
-
REGISTER_URLHANDLER("/v1/actions", ActionsHandler);
bool ActionsHandler::HandleRequest(
@@ -72,18 +72,10 @@ bool ActionsHandler::HandleRequest(
objs.emplace_back(nullptr);
}
- ArrayData results;
-
Log(LogNotice, "ApiActionHandler")
<< "Running action " << actionName;
bool verbose = false;
-
- ActionsHandler::AuthenticatedApiUser = user;
- Defer a ([]() {
- ActionsHandler::AuthenticatedApiUser = nullptr;
- });
-
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");
@@ -93,73 +85,43 @@ bool ActionsHandler::HandleRequest(
return true;
}
- for (ConfigObject::Ptr obj : objs) {
+ auto generatorFunc = [&action, &user, ¶ms, &waitGroup, &wgLock, verbose](
+ const ConfigObject::Ptr& obj
+ ) -> std::optional {
if (!waitGroup->IsLockable()) {
if (wgLock) {
wgLock.unlock();
}
- results.emplace_back(new Dictionary({
+ return new Dictionary{
{ "type", obj->GetReflectionType()->GetName() },
{ "name", obj->GetName() },
{ "code", 503 },
{ "status", "Action skipped: Shutting down."}
- }));
-
- continue;
+ };
}
try {
- results.emplace_back(action->Invoke(obj, params));
+ return action->Invoke(obj, user, params);
} catch (const std::exception& ex) {
- Dictionary::Ptr fail = new Dictionary({
+ Dictionary::Ptr fail = new Dictionary{
{ "code", 500 },
{ "status", "Action execution failed: '" + DiagnosticInformation(ex, false) + "'." }
- });
+ };
/* Exception for actions. Normally we would handle this inside SendJsonError(). */
if (verbose)
fail->Set("diagnostic_information", DiagnosticInformation(ex));
- results.emplace_back(std::move(fail));
- }
- }
-
- int statusCode = 500;
- std::set okStatusCodes, nonOkStatusCodes;
-
- for (Dictionary::Ptr res : results) {
- if (!res->Contains("code")) {
- continue;
+ return fail;
}
+ };
- auto code = res->Get("code");
-
- if (code >= 200 && code <= 299) {
- okStatusCodes.insert(code);
- } else {
- nonOkStatusCodes.insert(code);
- }
- }
-
- size_t okSize = okStatusCodes.size();
- size_t nonOkSize = nonOkStatusCodes.size();
-
- if (okSize == 1u && nonOkSize == 0u) {
- statusCode = *okStatusCodes.begin();
- } else if (nonOkSize == 1u) {
- statusCode = *nonOkStatusCodes.begin();
- } else if (okSize >= 2u && nonOkSize == 0u) {
- statusCode = 200;
- }
-
- response.result(statusCode);
-
- Dictionary::Ptr result = new Dictionary({
- { "results", new Array(std::move(results)) }
- });
+ Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
+ result->Freeze();
- HttpUtility::SendJsonBody(response, params, result);
+ response.result(http::status::accepted);
+ HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}
diff --git a/lib/remote/actionshandler.hpp b/lib/remote/actionshandler.hpp
index 3ba856f697..4d837cd77f 100644
--- a/lib/remote/actionshandler.hpp
+++ b/lib/remote/actionshandler.hpp
@@ -13,8 +13,6 @@ class ActionsHandler final : public HttpHandler
public:
DECLARE_PTR_TYPEDEFS(ActionsHandler);
- static thread_local ApiUser::Ptr AuthenticatedApiUser;
-
bool HandleRequest(
const WaitGroup::Ptr& waitGroup,
const HttpRequest& request,
diff --git a/lib/remote/apiaction.cpp b/lib/remote/apiaction.cpp
index ccde28190b..6f7dc2da39 100644
--- a/lib/remote/apiaction.cpp
+++ b/lib/remote/apiaction.cpp
@@ -9,9 +9,9 @@ ApiAction::ApiAction(std::vector types, Callback action)
: m_Types(std::move(types)), m_Callback(std::move(action))
{ }
-Value ApiAction::Invoke(const ConfigObject::Ptr& target, const Dictionary::Ptr& params)
+Value ApiAction::Invoke(const ConfigObject::Ptr& target, const ApiUser::Ptr& user, const Dictionary::Ptr& params)
{
- return m_Callback(target, params);
+ return m_Callback(target, user, params);
}
const std::vector& ApiAction::GetTypes() const
diff --git a/lib/remote/apiaction.hpp b/lib/remote/apiaction.hpp
index 2bb98b1b6b..f96aa32fd0 100644
--- a/lib/remote/apiaction.hpp
+++ b/lib/remote/apiaction.hpp
@@ -8,6 +8,7 @@
#include "base/value.hpp"
#include "base/dictionary.hpp"
#include "base/configobject.hpp"
+#include "remote/apiuser.hpp"
#include
#include
@@ -24,11 +25,11 @@ class ApiAction final : public Object
public:
DECLARE_PTR_TYPEDEFS(ApiAction);
- typedef std::function Callback;
+ typedef std::function Callback;
ApiAction(std::vector registerTypes, Callback function);
- Value Invoke(const ConfigObject::Ptr& target, const Dictionary::Ptr& params);
+ Value Invoke(const ConfigObject::Ptr& target, const ApiUser::Ptr& user, const Dictionary::Ptr& params);
const std::vector& GetTypes() const;
diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp
index 7e0c7b02c5..937eea2d51 100644
--- a/lib/remote/configpackageshandler.cpp
+++ b/lib/remote/configpackageshandler.cpp
@@ -28,7 +28,7 @@ bool ConfigPackagesHandler::HandleRequest(
return false;
if (request.method() == http::verb::get)
- HandleGet(request, response);
+ HandleGet(request, response, yc);
else if (request.method() == http::verb::post)
HandlePost(request, response);
else if (request.method() == http::verb::delete_)
@@ -39,7 +39,7 @@ bool ConfigPackagesHandler::HandleRequest(
return true;
}
-void ConfigPackagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response)
+void ConfigPackagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@@ -79,12 +79,14 @@ void ConfigPackagesHandler::HandleGet(const HttpRequest& request, HttpResponse&
}
}
- Dictionary::Ptr result = new Dictionary({
- { "results", new Array(std::move(results)) }
- });
+ Array::Ptr resultsArr = new Array(std::move(results));
+ resultsArr->Freeze();
+
+ Dictionary::Ptr result = new Dictionary{{"results", resultsArr}};
+ result->Freeze();
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
}
void ConfigPackagesHandler::HandlePost(const HttpRequest& request, HttpResponse& response)
diff --git a/lib/remote/configpackageshandler.hpp b/lib/remote/configpackageshandler.hpp
index 172690f639..086ef5b70c 100644
--- a/lib/remote/configpackageshandler.hpp
+++ b/lib/remote/configpackageshandler.hpp
@@ -21,7 +21,7 @@ class ConfigPackagesHandler final : public HttpHandler
) override;
private:
- void HandleGet(const HttpRequest& request, HttpResponse& response);
+ void HandleGet(const HttpRequest& request, HttpResponse& response, boost::asio::yield_context& yc);
void HandlePost(const HttpRequest& request, HttpResponse& response);
void HandleDelete(const HttpRequest& request, HttpResponse& response);
diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp
index b08270e56e..1f3adcd1c3 100644
--- a/lib/remote/configstageshandler.cpp
+++ b/lib/remote/configstageshandler.cpp
@@ -8,6 +8,7 @@
#include "base/application.hpp"
#include "base/defer.hpp"
#include "base/exception.hpp"
+#include
using namespace icinga;
@@ -35,7 +36,7 @@ bool ConfigStagesHandler::HandleRequest(
return false;
if (request.method() == http::verb::get)
- HandleGet(request, response);
+ HandleGet(request, response, yc);
else if (request.method() == http::verb::post)
HandlePost(request, response);
else if (request.method() == http::verb::delete_)
@@ -46,7 +47,7 @@ bool ConfigStagesHandler::HandleRequest(
return true;
}
-void ConfigStagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response)
+void ConfigStagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response, boost::asio::yield_context& yc)
{
namespace http = boost::beast::http;
@@ -71,25 +72,22 @@ void ConfigStagesHandler::HandleGet(const HttpRequest& request, HttpResponse& re
if (!ConfigPackageUtility::ValidateStageName(stageName))
return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
- ArrayData results;
-
std::vector > paths = ConfigPackageUtility::GetFiles(packageName, stageName);
String prefixPath = ConfigPackageUtility::GetPackageDir() + "/" + packageName + "/" + stageName + "/";
- for (const auto& kv : paths) {
- results.push_back(new Dictionary({
+ auto generatorFunc = [&prefixPath](const std::pair& kv) -> std::optional {
+ return new Dictionary{
{ "type", kv.second ? "directory" : "file" },
- { "name", kv.first.SubStr(prefixPath.GetLength()) }
- }));
- }
+ { "name", kv.first.SubStr(prefixPath.GetLength()) },
+ };
+ };
- Dictionary::Ptr result = new Dictionary({
- { "results", new Array(std::move(results)) }
- });
+ Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{paths, generatorFunc}}};
+ result->Freeze();
response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ HttpUtility::SendJsonBody(response, params, result, yc);
}
void ConfigStagesHandler::HandlePost(const HttpRequest& request, HttpResponse& response)
diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp
index ec333cc50c..0e5b183074 100644
--- a/lib/remote/configstageshandler.hpp
+++ b/lib/remote/configstageshandler.hpp
@@ -21,7 +21,7 @@ class ConfigStagesHandler final : public HttpHandler
) override;
private:
- void HandleGet(const HttpRequest& request, HttpResponse& response);
+ void HandleGet(const HttpRequest& request, HttpResponse& response, boost::asio::yield_context& yc);
void HandlePost(const HttpRequest& request, HttpResponse& response);
void HandleDelete(const HttpRequest& request, HttpResponse& response);
};
diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp
index cd99f7b282..4414b0e216 100644
--- a/lib/remote/deleteobjecthandler.cpp
+++ b/lib/remote/deleteobjecthandler.cpp
@@ -9,6 +9,7 @@
#include "config/configitem.hpp"
#include "base/exception.hpp"
#include
+#include
#include
using namespace icinga;
@@ -74,32 +75,26 @@ bool DeleteObjectHandler::HandleRequest(
return true;
}
- ArrayData results;
-
- bool success = true;
-
std::shared_lock wgLock{*waitGroup, std::try_to_lock};
if (!wgLock) {
HttpUtility::SendJsonError(response, params, 503, "Shutting down.");
return true;
}
- for (ConfigObject::Ptr obj : objs) {
+ auto generatorFunc = [&type, &waitGroup, &wgLock, cascade, verbose](
+ const ConfigObject::Ptr& obj
+ ) -> std::optional {
if (!waitGroup->IsLockable()) {
if (wgLock) {
wgLock.unlock();
}
- results.emplace_back(new Dictionary({
+ return new Dictionary{
{ "type", type->GetName() },
{ "name", obj->GetName() },
{ "code", 503 },
{ "status", "Action skipped: Shutting down."}
- }));
-
- success = false;
-
- continue;
+ };
}
int code;
@@ -113,36 +108,30 @@ bool DeleteObjectHandler::HandleRequest(
if (!ConfigObjectUtility::DeleteObject(obj, cascade, errors, diagnosticInformation)) {
code = 500;
status = "Object could not be deleted.";
- success = false;
} else {
code = 200;
status = "Object was deleted.";
}
- Dictionary::Ptr result = new Dictionary({
+ Dictionary::Ptr result = new Dictionary{
{ "type", type->GetName() },
{ "name", obj->GetName() },
{ "code", code },
{ "status", status },
{ "errors", errors }
- });
+ };
if (verbose)
result->Set("diagnostic_information", diagnosticInformation);
- results.push_back(result);
- }
-
- Dictionary::Ptr result = new Dictionary({
- { "results", new Array(std::move(results)) }
- });
+ return result;
+ };
- if (!success)
- response.result(http::status::internal_server_error);
- else
- response.result(http::status::ok);
+ Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
+ result->Freeze();
- HttpUtility::SendJsonBody(response, params, result);
+ response.result(http::status::accepted);
+ HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}
diff --git a/lib/remote/httputility.cpp b/lib/remote/httputility.cpp
index b53a8721b7..4680cca642 100644
--- a/lib/remote/httputility.cpp
+++ b/lib/remote/httputility.cpp
@@ -52,6 +52,28 @@ Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String&
return arr->Get(arr->GetLength() - 1);
}
+/**
+ * Stream a JSON-encoded body to the client.
+ *
+ * This function sets the Content-Type header to "application/json", starts the streaming of the response,
+ * and encodes the given value as JSON to the client. If pretty-print is requested, the JSON output will be
+ * formatted accordingly. It is assumed that the response status code and other necessary headers have already
+ * been set.
+ *
+ * @param response The HTTP response to send the body to.
+ * @param params The request parameters.
+ * @param val The value to encode as JSON and stream to the client.
+ * @param yc The yield context to use for asynchronous operations.
+ */
+void HttpUtility::SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val, boost::asio::yield_context& yc)
+{
+ namespace http = boost::beast::http;
+
+ response.set(http::field::content_type, "application/json");
+ response.StartStreaming();
+ response.GetJsonEncoder(params && GetLastParameter(params, "pretty")).Encode(val, &yc);
+}
+
void HttpUtility::SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val)
{
namespace http = boost::beast::http;
diff --git a/lib/remote/httputility.hpp b/lib/remote/httputility.hpp
index 6f64277136..17f7b1ce4f 100644
--- a/lib/remote/httputility.hpp
+++ b/lib/remote/httputility.hpp
@@ -6,6 +6,7 @@
#include "remote/url.hpp"
#include "base/dictionary.hpp"
#include "remote/httpmessage.hpp"
+#include
#include
namespace icinga
@@ -23,6 +24,7 @@ class HttpUtility
static Dictionary::Ptr FetchRequestParameters(const Url::Ptr& url, const std::string& body);
static Value GetLastParameter(const Dictionary::Ptr& params, const String& key);
+ static void SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val, boost::asio::yield_context& yc);
static void SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val);
static void SendJsonError(HttpResponse& response, const Dictionary::Ptr& params, const int code,
const String& info = {}, const String& diagnosticInformation = {});
diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp
index 9264e3c64d..3cc76cbaea 100644
--- a/lib/remote/modifyobjecthandler.cpp
+++ b/lib/remote/modifyobjecthandler.cpp
@@ -6,7 +6,9 @@
#include "remote/filterutility.hpp"
#include "remote/apiaction.hpp"
#include "base/exception.hpp"
+#include "base/generator.hpp"
#include
+#include
#include
using namespace icinga;
@@ -102,31 +104,28 @@ bool ModifyObjectHandler::HandleRequest(
return true;
}
- ArrayData results;
-
std::shared_lock wgLock{*waitGroup, std::try_to_lock};
if (!wgLock) {
HttpUtility::SendJsonError(response, params, 503, "Shutting down.");
return true;
}
- for (ConfigObject::Ptr obj : objs) {
- Dictionary::Ptr result1 = new Dictionary();
+ auto generatorFunc = [&waitGroup, &wgLock, &type, &attrs, &restoreAttrs, verbose](
+ const ConfigObject::Ptr& obj
+ ) -> std::optional {
+ Dictionary::Ptr result = new Dictionary();
- result1->Set("type", type->GetName());
- result1->Set("name", obj->GetName());
+ result->Set("type", type->GetName());
+ result->Set("name", obj->GetName());
if (!waitGroup->IsLockable()) {
if (wgLock) {
wgLock.unlock();
}
- result1->Set("code", 503);
- result1->Set("status", "Action skipped: Shutting down.");
-
- results.emplace_back(std::move(result1));
-
- continue;
+ result->Set("code", 503);
+ result->Set("status", "Action skipped: Shutting down.");
+ return result;
}
String key;
@@ -144,14 +143,13 @@ bool ModifyObjectHandler::HandleRequest(
}
}
} catch (const std::exception& ex) {
- result1->Set("code", 500);
- result1->Set("status", "Attribute '" + key + "' could not be restored: " + DiagnosticInformation(ex, false));
+ result->Set("code", 500);
+ result->Set("status", "Attribute '" + key + "' could not be restored: " + DiagnosticInformation(ex, false));
if (verbose)
- result1->Set("diagnostic_information", DiagnosticInformation(ex));
+ result->Set("diagnostic_information", DiagnosticInformation(ex));
- results.push_back(std::move(result1));
- continue;
+ return result;
}
try {
@@ -163,28 +161,25 @@ bool ModifyObjectHandler::HandleRequest(
}
}
} catch (const std::exception& ex) {
- result1->Set("code", 500);
- result1->Set("status", "Attribute '" + key + "' could not be set: " + DiagnosticInformation(ex, false));
+ result->Set("code", 500);
+ result->Set("status", "Attribute '" + key + "' could not be set: " + DiagnosticInformation(ex, false));
if (verbose)
- result1->Set("diagnostic_information", DiagnosticInformation(ex));
+ result->Set("diagnostic_information", DiagnosticInformation(ex));
- results.push_back(std::move(result1));
- continue;
+ return result;
}
- result1->Set("code", 200);
- result1->Set("status", "Attributes updated.");
-
- results.push_back(std::move(result1));
- }
+ result->Set("code", 200);
+ result->Set("status", "Attributes updated.");
+ return result;
+ };
- Dictionary::Ptr result = new Dictionary({
- { "results", new Array(std::move(results)) }
- });
+ Dictionary::Ptr result = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
+ result->Freeze();
- response.result(http::status::ok);
- HttpUtility::SendJsonBody(response, params, result);
+ response.result(http::status::accepted);
+ HttpUtility::SendJsonBody(response, params, result, yc);
return true;
}
diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp
index 4384abb557..3691b2fb6f 100644
--- a/lib/remote/objectqueryhandler.cpp
+++ b/lib/remote/objectqueryhandler.cpp
@@ -209,15 +209,7 @@ bool ObjectQueryHandler::HandleRequest(
std::unordered_map>> typePermissions;
std::unordered_map