summaryrefslogtreecommitdiffstats
path: root/lib/remote/configstageshandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/remote/configstageshandler.cpp')
-rw-r--r--lib/remote/configstageshandler.cpp225
1 files changed, 225 insertions, 0 deletions
diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp
new file mode 100644
index 0000000..b5aaadd
--- /dev/null
+++ b/lib/remote/configstageshandler.cpp
@@ -0,0 +1,225 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "remote/configstageshandler.hpp"
+#include "remote/configpackageutility.hpp"
+#include "remote/httputility.hpp"
+#include "remote/filterutility.hpp"
+#include "base/application.hpp"
+#include "base/defer.hpp"
+#include "base/exception.hpp"
+
+using namespace icinga;
+
+REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler);
+
+std::atomic<bool> ConfigStagesHandler::m_RunningPackageUpdates (false);
+
+bool ConfigStagesHandler::HandleRequest(
+ AsioTlsStream& stream,
+ const ApiUser::Ptr& user,
+ boost::beast::http::request<boost::beast::http::string_body>& request,
+ const Url::Ptr& url,
+ boost::beast::http::response<boost::beast::http::string_body>& response,
+ const Dictionary::Ptr& params,
+ boost::asio::yield_context& yc,
+ HttpServerConnection& server
+)
+{
+ namespace http = boost::beast::http;
+
+ if (url->GetPath().size() > 5)
+ return false;
+
+ if (request.method() == http::verb::get)
+ HandleGet(user, request, url, response, params);
+ else if (request.method() == http::verb::post)
+ HandlePost(user, request, url, response, params);
+ else if (request.method() == http::verb::delete_)
+ HandleDelete(user, request, url, response, params);
+ else
+ return false;
+
+ return true;
+}
+
+void ConfigStagesHandler::HandleGet(
+ const ApiUser::Ptr& user,
+ boost::beast::http::request<boost::beast::http::string_body>& request,
+ const Url::Ptr& url,
+ boost::beast::http::response<boost::beast::http::string_body>& response,
+ const Dictionary::Ptr& params
+)
+{
+ namespace http = boost::beast::http;
+
+ FilterUtility::CheckPermission(user, "config/query");
+
+ if (url->GetPath().size() >= 4)
+ params->Set("package", url->GetPath()[3]);
+
+ if (url->GetPath().size() >= 5)
+ params->Set("stage", url->GetPath()[4]);
+
+ String packageName = HttpUtility::GetLastParameter(params, "package");
+ String stageName = HttpUtility::GetLastParameter(params, "stage");
+
+ if (!ConfigPackageUtility::ValidatePackageName(packageName))
+ return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
+
+ if (!ConfigPackageUtility::ValidateStageName(stageName))
+ return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
+
+ ArrayData results;
+
+ std::vector<std::pair<String, bool> > paths = ConfigPackageUtility::GetFiles(packageName, stageName);
+
+ String prefixPath = ConfigPackageUtility::GetPackageDir() + "/" + packageName + "/" + stageName + "/";
+
+ for (const auto& kv : paths) {
+ results.push_back(new Dictionary({
+ { "type", kv.second ? "directory" : "file" },
+ { "name", kv.first.SubStr(prefixPath.GetLength()) }
+ }));
+ }
+
+ Dictionary::Ptr result = new Dictionary({
+ { "results", new Array(std::move(results)) }
+ });
+
+ response.result(http::status::ok);
+ HttpUtility::SendJsonBody(response, params, result);
+}
+
+void ConfigStagesHandler::HandlePost(
+ const ApiUser::Ptr& user,
+ boost::beast::http::request<boost::beast::http::string_body>& request,
+ const Url::Ptr& url,
+ boost::beast::http::response<boost::beast::http::string_body>& response,
+ const Dictionary::Ptr& params
+)
+{
+ namespace http = boost::beast::http;
+
+ FilterUtility::CheckPermission(user, "config/modify");
+
+ if (url->GetPath().size() >= 4)
+ params->Set("package", url->GetPath()[3]);
+
+ String packageName = HttpUtility::GetLastParameter(params, "package");
+
+ if (!ConfigPackageUtility::ValidatePackageName(packageName))
+ return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
+
+ bool reload = true;
+
+ if (params->Contains("reload"))
+ reload = HttpUtility::GetLastParameter(params, "reload");
+
+ bool activate = true;
+
+ if (params->Contains("activate"))
+ activate = HttpUtility::GetLastParameter(params, "activate");
+
+ Dictionary::Ptr files = params->Get("files");
+
+ String stageName;
+
+ try {
+ if (!files)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'files' must be specified."));
+
+ if (reload && !activate)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'reload' must be false when 'activate' is false."));
+
+ if (m_RunningPackageUpdates.exchange(true)) {
+ return HttpUtility::SendJsonError(response, params, 423,
+ "Conflicting request, there is already an ongoing package update in progress. Please try it again later.");
+ }
+
+ auto resetPackageUpdates (Shared<Defer>::Make([]() { ConfigStagesHandler::m_RunningPackageUpdates.store(false); }));
+
+ std::unique_lock<std::mutex> lock(ConfigPackageUtility::GetStaticPackageMutex());
+
+ stageName = ConfigPackageUtility::CreateStage(packageName, files);
+
+ /* validate the config. on success, activate stage and reload */
+ ConfigPackageUtility::AsyncTryActivateStage(packageName, stageName, activate, reload, resetPackageUpdates);
+ } catch (const std::exception& ex) {
+ return HttpUtility::SendJsonError(response, params, 500,
+ "Stage creation failed.",
+ DiagnosticInformation(ex));
+ }
+
+
+ String responseStatus = "Created stage. ";
+
+ if (reload)
+ responseStatus += "Reload triggered.";
+ else
+ responseStatus += "Reload skipped.";
+
+ Dictionary::Ptr result1 = new Dictionary({
+ { "package", packageName },
+ { "stage", stageName },
+ { "code", 200 },
+ { "status", responseStatus }
+ });
+
+ Dictionary::Ptr result = new Dictionary({
+ { "results", new Array({ result1 }) }
+ });
+
+ response.result(http::status::ok);
+ HttpUtility::SendJsonBody(response, params, result);
+}
+
+void ConfigStagesHandler::HandleDelete(
+ const ApiUser::Ptr& user,
+ boost::beast::http::request<boost::beast::http::string_body>& request,
+ const Url::Ptr& url,
+ boost::beast::http::response<boost::beast::http::string_body>& response,
+ const Dictionary::Ptr& params
+)
+{
+ namespace http = boost::beast::http;
+
+ FilterUtility::CheckPermission(user, "config/modify");
+
+ if (url->GetPath().size() >= 4)
+ params->Set("package", url->GetPath()[3]);
+
+ if (url->GetPath().size() >= 5)
+ params->Set("stage", url->GetPath()[4]);
+
+ String packageName = HttpUtility::GetLastParameter(params, "package");
+ String stageName = HttpUtility::GetLastParameter(params, "stage");
+
+ if (!ConfigPackageUtility::ValidatePackageName(packageName))
+ return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
+
+ if (!ConfigPackageUtility::ValidateStageName(stageName))
+ return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
+
+ try {
+ ConfigPackageUtility::DeleteStage(packageName, stageName);
+ } catch (const std::exception& ex) {
+ return HttpUtility::SendJsonError(response, params, 500,
+ "Failed to delete stage '" + stageName + "' in package '" + packageName + "'.",
+ DiagnosticInformation(ex));
+ }
+
+ Dictionary::Ptr result1 = new Dictionary({
+ { "code", 200 },
+ { "package", packageName },
+ { "stage", stageName },
+ { "status", "Stage deleted." }
+ });
+
+ Dictionary::Ptr result = new Dictionary({
+ { "results", new Array({ result1 }) }
+ });
+
+ response.result(http::status::ok);
+ HttpUtility::SendJsonBody(response, params, result);
+}
+