1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "remote/configstageshandler.hpp"
4 #include "remote/configpackageutility.hpp"
5 #include "remote/httputility.hpp"
6 #include "remote/filterutility.hpp"
7 #include "base/application.hpp"
8 #include "base/exception.hpp"
9
10 using namespace icinga;
11
12 REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler);
13
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)14 bool ConfigStagesHandler::HandleRequest(
15 AsioTlsStream& stream,
16 const ApiUser::Ptr& user,
17 boost::beast::http::request<boost::beast::http::string_body>& request,
18 const Url::Ptr& url,
19 boost::beast::http::response<boost::beast::http::string_body>& response,
20 const Dictionary::Ptr& params,
21 boost::asio::yield_context& yc,
22 HttpServerConnection& server
23 )
24 {
25 namespace http = boost::beast::http;
26
27 if (url->GetPath().size() > 5)
28 return false;
29
30 if (request.method() == http::verb::get)
31 HandleGet(user, request, url, response, params);
32 else if (request.method() == http::verb::post)
33 HandlePost(user, request, url, response, params);
34 else if (request.method() == http::verb::delete_)
35 HandleDelete(user, request, url, response, params);
36 else
37 return false;
38
39 return true;
40 }
41
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)42 void ConfigStagesHandler::HandleGet(
43 const ApiUser::Ptr& user,
44 boost::beast::http::request<boost::beast::http::string_body>& request,
45 const Url::Ptr& url,
46 boost::beast::http::response<boost::beast::http::string_body>& response,
47 const Dictionary::Ptr& params
48 )
49 {
50 namespace http = boost::beast::http;
51
52 FilterUtility::CheckPermission(user, "config/query");
53
54 if (url->GetPath().size() >= 4)
55 params->Set("package", url->GetPath()[3]);
56
57 if (url->GetPath().size() >= 5)
58 params->Set("stage", url->GetPath()[4]);
59
60 String packageName = HttpUtility::GetLastParameter(params, "package");
61 String stageName = HttpUtility::GetLastParameter(params, "stage");
62
63 if (!ConfigPackageUtility::ValidatePackageName(packageName))
64 return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
65
66 if (!ConfigPackageUtility::ValidateStageName(stageName))
67 return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
68
69 ArrayData results;
70
71 std::vector<std::pair<String, bool> > paths = ConfigPackageUtility::GetFiles(packageName, stageName);
72
73 String prefixPath = ConfigPackageUtility::GetPackageDir() + "/" + packageName + "/" + stageName + "/";
74
75 for (const auto& kv : paths) {
76 results.push_back(new Dictionary({
77 { "type", kv.second ? "directory" : "file" },
78 { "name", kv.first.SubStr(prefixPath.GetLength()) }
79 }));
80 }
81
82 Dictionary::Ptr result = new Dictionary({
83 { "results", new Array(std::move(results)) }
84 });
85
86 response.result(http::status::ok);
87 HttpUtility::SendJsonBody(response, params, result);
88 }
89
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)90 void ConfigStagesHandler::HandlePost(
91 const ApiUser::Ptr& user,
92 boost::beast::http::request<boost::beast::http::string_body>& request,
93 const Url::Ptr& url,
94 boost::beast::http::response<boost::beast::http::string_body>& response,
95 const Dictionary::Ptr& params
96 )
97 {
98 namespace http = boost::beast::http;
99
100 FilterUtility::CheckPermission(user, "config/modify");
101
102 if (url->GetPath().size() >= 4)
103 params->Set("package", url->GetPath()[3]);
104
105 String packageName = HttpUtility::GetLastParameter(params, "package");
106
107 if (!ConfigPackageUtility::ValidatePackageName(packageName))
108 return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
109
110 bool reload = true;
111
112 if (params->Contains("reload"))
113 reload = HttpUtility::GetLastParameter(params, "reload");
114
115 bool activate = true;
116
117 if (params->Contains("activate"))
118 activate = HttpUtility::GetLastParameter(params, "activate");
119
120 Dictionary::Ptr files = params->Get("files");
121
122 String stageName;
123
124 try {
125 if (!files)
126 BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'files' must be specified."));
127
128 if (reload && !activate)
129 BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'reload' must be false when 'activate' is false."));
130
131 std::unique_lock<std::mutex> lock(ConfigPackageUtility::GetStaticPackageMutex());
132
133 stageName = ConfigPackageUtility::CreateStage(packageName, files);
134
135 /* validate the config. on success, activate stage and reload */
136 ConfigPackageUtility::AsyncTryActivateStage(packageName, stageName, activate, reload);
137 } catch (const std::exception& ex) {
138 return HttpUtility::SendJsonError(response, params, 500,
139 "Stage creation failed.",
140 DiagnosticInformation(ex));
141 }
142
143
144 String responseStatus = "Created stage. ";
145
146 if (reload)
147 responseStatus += "Reload triggered.";
148 else
149 responseStatus += "Reload skipped.";
150
151 Dictionary::Ptr result1 = new Dictionary({
152 { "package", packageName },
153 { "stage", stageName },
154 { "code", 200 },
155 { "status", responseStatus }
156 });
157
158 Dictionary::Ptr result = new Dictionary({
159 { "results", new Array({ result1 }) }
160 });
161
162 response.result(http::status::ok);
163 HttpUtility::SendJsonBody(response, params, result);
164 }
165
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)166 void ConfigStagesHandler::HandleDelete(
167 const ApiUser::Ptr& user,
168 boost::beast::http::request<boost::beast::http::string_body>& request,
169 const Url::Ptr& url,
170 boost::beast::http::response<boost::beast::http::string_body>& response,
171 const Dictionary::Ptr& params
172 )
173 {
174 namespace http = boost::beast::http;
175
176 FilterUtility::CheckPermission(user, "config/modify");
177
178 if (url->GetPath().size() >= 4)
179 params->Set("package", url->GetPath()[3]);
180
181 if (url->GetPath().size() >= 5)
182 params->Set("stage", url->GetPath()[4]);
183
184 String packageName = HttpUtility::GetLastParameter(params, "package");
185 String stageName = HttpUtility::GetLastParameter(params, "stage");
186
187 if (!ConfigPackageUtility::ValidatePackageName(packageName))
188 return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
189
190 if (!ConfigPackageUtility::ValidateStageName(stageName))
191 return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
192
193 try {
194 ConfigPackageUtility::DeleteStage(packageName, stageName);
195 } catch (const std::exception& ex) {
196 return HttpUtility::SendJsonError(response, params, 500,
197 "Failed to delete stage '" + stageName + "' in package '" + packageName + "'.",
198 DiagnosticInformation(ex));
199 }
200
201 Dictionary::Ptr result1 = new Dictionary({
202 { "code", 200 },
203 { "package", packageName },
204 { "stage", stageName },
205 { "status", "Stage deleted." }
206 });
207
208 Dictionary::Ptr result = new Dictionary({
209 { "results", new Array({ result1 }) }
210 });
211
212 response.result(http::status::ok);
213 HttpUtility::SendJsonBody(response, params, result);
214 }
215
216