Merge pull request #6205 from Icinga/feature/api-verbose-errors

API: Unify verbose error messages
This commit is contained in:
Michael Friedrich 2018-04-17 16:40:11 +02:00 committed by GitHub
commit a8b5d8e64a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 193 additions and 81 deletions

View File

@ -103,7 +103,7 @@ The output will be sent back as a JSON object:
> **Tip** > **Tip**
> >
> You can use the `pretty` parameter to beautify the JSON response with Icinga v2.9+. > You can use the [pretty](12-icinga2-api.md#icinga2-api-parameters-global) parameter to beautify the JSON response with Icinga v2.9+.
You can also use [jq](https://stedolan.github.io/jq/) or `python -m json.tool` You can also use [jq](https://stedolan.github.io/jq/) or `python -m json.tool`
in combination with curl on the CLI. in combination with curl on the CLI.
@ -124,7 +124,8 @@ The API will return standard [HTTP statuses](https://www.ietf.org/rfc/rfc2616.tx
including error codes. including error codes.
When an error occurs, the response body will contain additional information When an error occurs, the response body will contain additional information
about the problem and its source. about the problem and its source. Set `verbose` to true to retrieve more
insights into what may be causing the error.
A status code between 200 and 299 generally means that the request was A status code between 200 and 299 generally means that the request was
successful. successful.
@ -271,6 +272,27 @@ Here are the exact same query parameters as a JSON object:
The [match function](18-library-reference.md#global-functions-match) is available as global function The [match function](18-library-reference.md#global-functions-match) is available as global function
in Icinga 2. in Icinga 2.
### Global Parameters <a id="icinga2-api-parameters-global"></a>
Name | Description
----------------|--------------------
pretty | Pretty-print the JSON response.
verbose | Add verbose debug information inside the `diagnostic_information` key into the response if available. This helps with troubleshooting failing requests.
Example as URL parameter:
```
/v1/objects/hosts?pretty=1
```
Example as JSON object:
```
{ "pretty": true }
```
Both parameters have been added in Icinga 2 v2.9.
### Request Method Override <a id="icinga2-api-requests-method-override"></a> ### Request Method Override <a id="icinga2-api-requests-method-override"></a>
`GET` requests do not allow you to send a request body. In case you cannot pass everything as URL `GET` requests do not allow you to send a request body. In case you cannot pass everything as URL

View File

@ -702,6 +702,33 @@ Look into the log and check whether the feature logs anything specific for this
grep GraphiteWriter /var/log/icinga2/icinga2.log grep GraphiteWriter /var/log/icinga2/icinga2.log
``` ```
## REST API Troubleshooting <a id="troubleshooting-api"></a>
In order to analyse errors on API requests, you can explicitly enable the [verbose parameter](12-icinga2-api.md#icinga2-api-parameters-global).
```
$ curl -k -s -u root:icinga -H 'Accept: application/json' -X DELETE 'https://localhost:5665/v1/objects/hosts/example-cmdb?pretty=1&verbose=1'
{
"diagnostic_information": "Error: Object does not exist.\n\n ....",
"error": 404.0,
"status": "No objects found."
}
```
## REST API Troubleshooting: No Objects Found <a id="troubleshooting-api-no-objects-found"></a>
Please note that the `404` status with no objects being found can also originate
from missing or too strict object permissions for the authenticated user.
This is a security feature to disable object name guessing. If this would not be the
case, restricted users would be able to get a list of names of your objects just by
trying every character combination.
In order to analyse and fix the problem, please check the following:
- use an administrative account with full permissions to check whether the objects are actually there.
- verify the permissions on the affected ApiUser object and fix them.
## Certificate Troubleshooting <a id="troubleshooting-certificate"></a> ## Certificate Troubleshooting <a id="troubleshooting-certificate"></a>

View File

@ -182,7 +182,7 @@ String Comment::AddComment(const Checkable::Ptr& checkable, CommentType entryTyp
Array::Ptr errors = new Array(); Array::Ptr errors = new Array();
if (!ConfigObjectUtility::CreateObject(Comment::TypeInstance, fullName, config, errors)) { if (!ConfigObjectUtility::CreateObject(Comment::TypeInstance, fullName, config, errors, nullptr)) {
ObjectLock olock(errors); ObjectLock olock(errors);
for (const String& error : errors) { for (const String& error : errors) {
Log(LogCritical, "Comment", error); Log(LogCritical, "Comment", error);
@ -214,7 +214,7 @@ void Comment::RemoveComment(const String& id, const MessageOrigin::Ptr& origin)
Array::Ptr errors = new Array(); Array::Ptr errors = new Array();
if (!ConfigObjectUtility::DeleteObject(comment, false, errors)) { if (!ConfigObjectUtility::DeleteObject(comment, false, errors, nullptr)) {
ObjectLock olock(errors); ObjectLock olock(errors);
for (const String& error : errors) { for (const String& error : errors) {
Log(LogCritical, "Comment", error); Log(LogCritical, "Comment", error);

View File

@ -255,7 +255,7 @@ String Downtime::AddDowntime(const Checkable::Ptr& checkable, const String& auth
Array::Ptr errors = new Array(); Array::Ptr errors = new Array();
if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors)) { if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors, nullptr)) {
ObjectLock olock(errors); ObjectLock olock(errors);
for (const String& error : errors) { for (const String& error : errors) {
Log(LogCritical, "Downtime", error); Log(LogCritical, "Downtime", error);
@ -308,7 +308,7 @@ void Downtime::RemoveDowntime(const String& id, bool cancelled, bool expired, co
Array::Ptr errors = new Array(); Array::Ptr errors = new Array();
if (!ConfigObjectUtility::DeleteObject(downtime, false, errors)) { if (!ConfigObjectUtility::DeleteObject(downtime, false, errors, nullptr)) {
ObjectLock olock(errors); ObjectLock olock(errors);
for (const String& error : errors) { for (const String& error : errors) {
Log(LogCritical, "Downtime", error); Log(LogCritical, "Downtime", error);

View File

@ -62,7 +62,7 @@ bool ActionsHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& reques
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 404, HttpUtility::SendJsonError(response, params, 404,
"No objects found.", "No objects found.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return true; return true;
} }
} else { } else {
@ -75,6 +75,11 @@ bool ActionsHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& reques
Log(LogNotice, "ApiActionHandler") Log(LogNotice, "ApiActionHandler")
<< "Running action " << actionName; << "Running action " << actionName;
bool verbose = false;
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");
for (const ConfigObject::Ptr& obj : objs) { for (const ConfigObject::Ptr& obj : objs) {
try { try {
results.emplace_back(action->Invoke(obj, params)); results.emplace_back(action->Invoke(obj, params));
@ -84,8 +89,9 @@ bool ActionsHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& reques
{ "status", "Action execution failed: '" + DiagnosticInformation(ex, false) + "'." } { "status", "Action execution failed: '" + DiagnosticInformation(ex, false) + "'." }
}); });
if (HttpUtility::GetLastParameter(params, "verboseErrors")) /* Exception for actions. Normally we would handle this inside SendJsonError(). */
fail->Set("diagnostic information", DiagnosticInformation(ex)); if (verbose)
fail->Set("diagnostic_information", DiagnosticInformation(ex));
results.emplace_back(std::move(fail)); results.emplace_back(std::move(fail));
} }

View File

@ -116,15 +116,14 @@ Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin
/* object does not exist, create it through the API */ /* object does not exist, create it through the API */
Array::Ptr errors = new Array(); Array::Ptr errors = new Array();
if (!ConfigObjectUtility::CreateObject(ptype, if (!ConfigObjectUtility::CreateObject(ptype, objName, config, errors, nullptr)) {
objName, config, errors)) {
Log(LogCritical, "ApiListener") Log(LogCritical, "ApiListener")
<< "Could not create object '" << objName << "':"; << "Could not create object '" << objName << "':";
ObjectLock olock(errors); ObjectLock olock(errors);
for (const String& error : errors) { for (const String& error : errors) {
Log(LogCritical, "ApiListener", error); Log(LogCritical, "ApiListener", error);
} }
return Empty; return Empty;
} }
@ -256,7 +255,7 @@ Value ApiListener::ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin
Array::Ptr errors = new Array(); Array::Ptr errors = new Array();
if (!ConfigObjectUtility::DeleteObject(object, true, errors)) { if (!ConfigObjectUtility::DeleteObject(object, true, errors, nullptr)) {
Log(LogCritical, "ApiListener", "Could not delete object:"); Log(LogCritical, "ApiListener", "Could not delete object:");
ObjectLock olock(errors); ObjectLock olock(errors);

View File

@ -91,7 +91,7 @@ bool ConfigFilesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& re
response.WriteBody(content.CStr(), content.GetLength()); response.WriteBody(content.CStr(), content.GetLength());
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 500, "Could not read file.", HttpUtility::SendJsonError(response, params, 500, "Could not read file.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
} }
return true; return true;

View File

@ -98,7 +98,7 @@ String ConfigObjectUtility::CreateObjectConfig(const Type::Ptr& type, const Stri
} }
bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& fullName, bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& fullName,
const String& config, const Array::Ptr& errors) const String& config, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation)
{ {
{ {
boost::mutex::scoped_lock lock(ConfigPackageUtility::GetStaticMutex()); boost::mutex::scoped_lock lock(ConfigPackageUtility::GetStaticMutex());
@ -121,7 +121,7 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full
Utility::MkDirP(Utility::DirName(path), 0700); Utility::MkDirP(Utility::DirName(path), 0700);
if (Utility::PathExists(path)) { if (Utility::PathExists(path)) {
errors->Add("Configuration file '" + path + "' already exists."); errors->Add("Cannot create object '" + fullName + "'. Configuration file '" + path + "' already exists.");
return false; return false;
} }
@ -151,7 +151,10 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full
} }
for (const boost::exception_ptr& ex : upq.GetExceptions()) { for (const boost::exception_ptr& ex : upq.GetExceptions()) {
errors->Add(DiagnosticInformation(ex)); errors->Add(DiagnosticInformation(ex, false));
if (diagnosticInformation)
diagnosticInformation->Add(DiagnosticInformation(ex));
} }
} }
@ -168,7 +171,10 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full
} }
if (errors) if (errors)
errors->Add(DiagnosticInformation(ex)); errors->Add(DiagnosticInformation(ex, false));
if (diagnosticInformation)
diagnosticInformation->Add(DiagnosticInformation(ex));
return false; return false;
} }
@ -176,17 +182,21 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full
return true; return true;
} }
bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors) bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bool cascade,
const Array::Ptr& errors, const Array::Ptr& diagnosticInformation)
{ {
std::vector<Object::Ptr> parents = DependencyGraph::GetParents(object); std::vector<Object::Ptr> parents = DependencyGraph::GetParents(object);
Type::Ptr type = object->GetReflectionType(); Type::Ptr type = object->GetReflectionType();
String name = object->GetName();
if (!parents.empty() && !cascade) { if (!parents.empty() && !cascade) {
if (errors) if (errors) {
errors->Add("Object '" + object->GetName() + "' of type '" + type->GetName() + errors->Add("Object '" + name + "' of type '" + type->GetName() +
"' cannot be deleted because other objects depend on it. " "' cannot be deleted because other objects depend on it. "
"Use cascading delete to delete it anyway."); "Use cascading delete to delete it anyway.");
}
return false; return false;
} }
@ -197,10 +207,10 @@ bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bo
if (!parentObj) if (!parentObj)
continue; continue;
DeleteObjectHelper(parentObj, cascade, errors); DeleteObjectHelper(parentObj, cascade, errors, diagnosticInformation);
} }
ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(type, object->GetName()); ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(type, name);
try { try {
/* mark this object for cluster delete event */ /* mark this object for cluster delete event */
@ -215,12 +225,15 @@ bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bo
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
if (errors) if (errors)
errors->Add(DiagnosticInformation(ex)); errors->Add(DiagnosticInformation(ex, false));
if (diagnosticInformation)
diagnosticInformation->Add(DiagnosticInformation(ex));
return false; return false;
} }
String path = GetObjectConfigPath(object->GetReflectionType(), object->GetName()); String path = GetObjectConfigPath(object->GetReflectionType(), name);
if (Utility::PathExists(path)) { if (Utility::PathExists(path)) {
if (unlink(path.CStr()) < 0 && errno != ENOENT) { if (unlink(path.CStr()) < 0 && errno != ENOENT) {
@ -234,7 +247,7 @@ bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bo
return true; return true;
} }
bool ConfigObjectUtility::DeleteObject(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors) bool ConfigObjectUtility::DeleteObject(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation)
{ {
if (object->GetPackage() != "_api") { if (object->GetPackage() != "_api") {
if (errors) if (errors)
@ -243,5 +256,5 @@ bool ConfigObjectUtility::DeleteObject(const ConfigObject::Ptr& object, bool cas
return false; return false;
} }
return DeleteObjectHelper(object, cascade, errors); return DeleteObjectHelper(object, cascade, errors, diagnosticInformation);
} }

View File

@ -45,13 +45,15 @@ public:
bool ignoreOnError, const Array::Ptr& templates, const Dictionary::Ptr& attrs); bool ignoreOnError, const Array::Ptr& templates, const Dictionary::Ptr& attrs);
static bool CreateObject(const Type::Ptr& type, const String& fullName, static bool CreateObject(const Type::Ptr& type, const String& fullName,
const String& config, const Array::Ptr& errors); const String& config, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation);
static bool DeleteObject(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors); static bool DeleteObject(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors,
const Array::Ptr& diagnosticInformation);
private: private:
static String EscapeName(const String& name); static String EscapeName(const String& name);
static bool DeleteObjectHelper(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors); static bool DeleteObjectHelper(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors,
const Array::Ptr& diagnosticInformation);
}; };
} }

View File

@ -54,7 +54,7 @@ void ConfigPackagesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& req
packages = ConfigPackageUtility::GetPackages(); packages = ConfigPackageUtility::GetPackages();
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 500, "Could not retrieve packages.", HttpUtility::SendJsonError(response, params, 500, "Could not retrieve packages.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return; return;
} }
@ -89,7 +89,7 @@ void ConfigPackagesHandler::HandlePost(const ApiUser::Ptr& user, HttpRequest& re
String packageName = HttpUtility::GetLastParameter(params, "package"); String packageName = HttpUtility::GetLastParameter(params, "package");
if (!ConfigPackageUtility::ValidateName(packageName)) { if (!ConfigPackageUtility::ValidateName(packageName)) {
HttpUtility::SendJsonError(response, params, 400, "Invalid package name."); HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
return; return;
} }
@ -97,13 +97,14 @@ void ConfigPackagesHandler::HandlePost(const ApiUser::Ptr& user, HttpRequest& re
boost::mutex::scoped_lock lock(ConfigPackageUtility::GetStaticMutex()); boost::mutex::scoped_lock lock(ConfigPackageUtility::GetStaticMutex());
ConfigPackageUtility::CreatePackage(packageName); ConfigPackageUtility::CreatePackage(packageName);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 500, "Could not create package.", HttpUtility::SendJsonError(response, params, 500, "Could not create package '" + packageName + "'.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return; return;
} }
Dictionary::Ptr result1 = new Dictionary({ Dictionary::Ptr result1 = new Dictionary({
{ "code", 200 }, { "code", 200 },
{ "package", packageName },
{ "status", "Created package." } { "status", "Created package." }
}); });
@ -125,31 +126,28 @@ void ConfigPackagesHandler::HandleDelete(const ApiUser::Ptr& user, HttpRequest&
String packageName = HttpUtility::GetLastParameter(params, "package"); String packageName = HttpUtility::GetLastParameter(params, "package");
if (!ConfigPackageUtility::ValidateName(packageName)) { if (!ConfigPackageUtility::ValidateName(packageName)) {
HttpUtility::SendJsonError(response, params, 400, "Invalid package name."); HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
return; return;
} }
int code = 200;
String status = "Deleted package.";
DictionaryData result1;
try { try {
ConfigPackageUtility::DeletePackage(packageName); ConfigPackageUtility::DeletePackage(packageName);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
code = 500; HttpUtility::SendJsonError(response, params, 500, "Failed to delete package '" + packageName + "'.",
status = "Failed to delete package."; DiagnosticInformation(ex));
if (HttpUtility::GetLastParameter(params, "verboseErrors")) return;
result1.emplace_back("diagnostic information", DiagnosticInformation(ex));
} }
result1.emplace_back("package", packageName); Dictionary::Ptr result1 = new Dictionary({
result1.emplace_back("code", code); { "code", 200 },
result1.emplace_back("status", status); { "package", packageName },
{ "status", "Deleted package." }
Dictionary::Ptr result = new Dictionary({
{ "results", new Array({ new Dictionary(std::move(result1)) }) }
}); });
response.SetStatus(code, (code == 200) ? "OK" : "Internal Server Error"); Dictionary::Ptr result = new Dictionary({
{ "results", new Array({ result1 }) }
});
response.SetStatus(200, "OK");
HttpUtility::SendJsonBody(response, params, result); HttpUtility::SendJsonBody(response, params, result);
} }

View File

@ -59,10 +59,10 @@ void ConfigStagesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& reque
String stageName = HttpUtility::GetLastParameter(params, "stage"); String stageName = HttpUtility::GetLastParameter(params, "stage");
if (!ConfigPackageUtility::ValidateName(packageName)) if (!ConfigPackageUtility::ValidateName(packageName))
return HttpUtility::SendJsonError(response, params, 400, "Invalid package name."); return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
if (!ConfigPackageUtility::ValidateName(stageName)) if (!ConfigPackageUtility::ValidateName(stageName))
return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name."); return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
ArrayData results; ArrayData results;
@ -95,9 +95,10 @@ void ConfigStagesHandler::HandlePost(const ApiUser::Ptr& user, HttpRequest& requ
String packageName = HttpUtility::GetLastParameter(params, "package"); String packageName = HttpUtility::GetLastParameter(params, "package");
if (!ConfigPackageUtility::ValidateName(packageName)) if (!ConfigPackageUtility::ValidateName(packageName))
return HttpUtility::SendJsonError(response, params, 400, "Invalid package name."); return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
bool reload = true; bool reload = true;
if (params->Contains("reload")) if (params->Contains("reload"))
reload = HttpUtility::GetLastParameter(params, "reload"); reload = HttpUtility::GetLastParameter(params, "reload");
@ -116,13 +117,17 @@ void ConfigStagesHandler::HandlePost(const ApiUser::Ptr& user, HttpRequest& requ
ConfigPackageUtility::AsyncTryActivateStage(packageName, stageName, reload); ConfigPackageUtility::AsyncTryActivateStage(packageName, stageName, reload);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
return HttpUtility::SendJsonError(response, params, 500, return HttpUtility::SendJsonError(response, params, 500,
"Stage creation failed.", "Stage creation failed.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
} }
String responseStatus = "Created stage. "; String responseStatus = "Created stage. ";
responseStatus += (reload ? " Icinga2 will reload." : " Icinga2 reload skipped.");
if (reload)
responseStatus += "Reload triggered.";
else
responseStatus += "Reload skipped.";
Dictionary::Ptr result1 = new Dictionary({ Dictionary::Ptr result1 = new Dictionary({
{ "package", packageName }, { "package", packageName },
@ -153,21 +158,23 @@ void ConfigStagesHandler::HandleDelete(const ApiUser::Ptr& user, HttpRequest& re
String stageName = HttpUtility::GetLastParameter(params, "stage"); String stageName = HttpUtility::GetLastParameter(params, "stage");
if (!ConfigPackageUtility::ValidateName(packageName)) if (!ConfigPackageUtility::ValidateName(packageName))
return HttpUtility::SendJsonError(response, params, 400, "Invalid package name."); return HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
if (!ConfigPackageUtility::ValidateName(stageName)) if (!ConfigPackageUtility::ValidateName(stageName))
return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name."); return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'.");
try { try {
ConfigPackageUtility::DeleteStage(packageName, stageName); ConfigPackageUtility::DeleteStage(packageName, stageName);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
return HttpUtility::SendJsonError(response, params, 500, return HttpUtility::SendJsonError(response, params, 500,
"Failed to delete stage.", "Failed to delete stage '" + stageName + "' in package '" + packageName + "'.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
} }
Dictionary::Ptr result1 = new Dictionary({ Dictionary::Ptr result1 = new Dictionary({
{ "code", 200 }, { "code", 200 },
{ "package", packageName },
{ "stage", stageName },
{ "status", "Stage deleted." } { "status", "Stage deleted." }
}); });

View File

@ -74,6 +74,7 @@ bool CreateObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
Dictionary::Ptr result1 = new Dictionary(); Dictionary::Ptr result1 = new Dictionary();
String status; String status;
Array::Ptr errors = new Array(); Array::Ptr errors = new Array();
Array::Ptr diagnosticInformation = new Array();
bool ignoreOnError = false; bool ignoreOnError = false;
@ -86,10 +87,22 @@ bool CreateObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
String config; String config;
bool verbose = false;
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");
/* Object creation can cause multiple errors and optionally diagnostic information.
* We can't use SendJsonError() here.
*/
try { try {
config = ConfigObjectUtility::CreateObjectConfig(type, name, ignoreOnError, templates, attrs); config = ConfigObjectUtility::CreateObjectConfig(type, name, ignoreOnError, templates, attrs);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
errors->Add(DiagnosticInformation(ex)); errors->Add(DiagnosticInformation(ex, false));
diagnosticInformation->Add(DiagnosticInformation(ex));
if (verbose)
result1->Set("diagnostic_information", diagnosticInformation);
result1->Set("errors", errors); result1->Set("errors", errors);
result1->Set("code", 500); result1->Set("code", 500);
@ -101,11 +114,14 @@ bool CreateObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
return true; return true;
} }
if (!ConfigObjectUtility::CreateObject(type, name, config, errors)) { if (!ConfigObjectUtility::CreateObject(type, name, config, errors, diagnosticInformation)) {
result1->Set("errors", errors); result1->Set("errors", errors);
result1->Set("code", 500); result1->Set("code", 500);
result1->Set("status", "Object could not be created."); result1->Set("status", "Object could not be created.");
if (verbose)
result1->Set("diagnostic_information", diagnosticInformation);
response.SetStatus(500, "Object could not be created"); response.SetStatus(500, "Object could not be created");
HttpUtility::SendJsonBody(response, params, result); HttpUtility::SendJsonBody(response, params, result);

View File

@ -65,11 +65,12 @@ bool DeleteObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 404, HttpUtility::SendJsonError(response, params, 404,
"No objects found.", "No objects found.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return true; return true;
} }
bool cascade = HttpUtility::GetLastParameter(params, "cascade"); bool cascade = HttpUtility::GetLastParameter(params, "cascade");
bool verbose = HttpUtility::GetLastParameter(params, "verbose");
ArrayData results; ArrayData results;
@ -79,8 +80,9 @@ bool DeleteObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
int code; int code;
String status; String status;
Array::Ptr errors = new Array(); Array::Ptr errors = new Array();
Array::Ptr diagnosticInformation = new Array();
if (!ConfigObjectUtility::DeleteObject(obj, cascade, errors)) { if (!ConfigObjectUtility::DeleteObject(obj, cascade, errors, diagnosticInformation)) {
code = 500; code = 500;
status = "Object could not be deleted."; status = "Object could not be deleted.";
success = false; success = false;
@ -89,13 +91,18 @@ bool DeleteObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
status = "Object was deleted."; status = "Object was deleted.";
} }
results.push_back(new Dictionary({ Dictionary::Ptr result = new Dictionary({
{ "type", type->GetName() }, { "type", type->GetName() },
{ "name", obj->GetName() }, { "name", obj->GetName() },
{ "code", code }, { "code", code },
{ "status", status }, { "status", status },
{ "errors", errors } { "errors", errors }
})); });
if (verbose)
result->Set("diagnostic_information", diagnosticInformation);
results.push_back(result);
} }
Dictionary::Ptr result = new Dictionary({ Dictionary::Ptr result = new Dictionary({

View File

@ -89,11 +89,18 @@ void HttpUtility::SendJsonError(HttpResponse& response, const Dictionary::Ptr& p
response.SetStatus(code, HttpUtility::GetErrorNameByCode(code)); response.SetStatus(code, HttpUtility::GetErrorNameByCode(code));
result->Set("error", code); result->Set("error", code);
bool verbose = false;
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");
if (!info.IsEmpty()) if (!info.IsEmpty())
result->Set("status", info); result->Set("status", info);
if (!diagnosticInformation.IsEmpty()) if (verbose) {
result->Set("diagnostic information", diagnosticInformation); if (!diagnosticInformation.IsEmpty())
result->Set("diagnostic_information", diagnosticInformation);
}
HttpUtility::SendJsonBody(response, params, result); HttpUtility::SendJsonBody(response, params, result);
} }

View File

@ -63,7 +63,7 @@ bool ModifyObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 404, HttpUtility::SendJsonError(response, params, 404,
"No objects found.", "No objects found.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return true; return true;
} }
@ -71,12 +71,17 @@ bool ModifyObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
if (attrsVal.GetReflectionType() != Dictionary::TypeInstance) { if (attrsVal.GetReflectionType() != Dictionary::TypeInstance) {
HttpUtility::SendJsonError(response, params, 400, HttpUtility::SendJsonError(response, params, 400,
"Invalid type for 'attrs' attribute specified. Dictionary type is required.", Empty); "Invalid type for 'attrs' attribute specified. Dictionary type is required.");
return true; return true;
} }
Dictionary::Ptr attrs = attrsVal; Dictionary::Ptr attrs = attrsVal;
bool verbose = false;
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");
ArrayData results; ArrayData results;
for (const ConfigObject::Ptr& obj : objs) { for (const ConfigObject::Ptr& obj : objs) {
@ -100,7 +105,10 @@ bool ModifyObjectHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& r
result1->Set("status", "Attributes updated."); result1->Set("status", "Attributes updated.");
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
result1->Set("code", 500); result1->Set("code", 500);
result1->Set("status", "Attribute '" + key + "' could not be set: " + DiagnosticInformation(ex)); result1->Set("status", "Attribute '" + key + "' could not be set: " + DiagnosticInformation(ex, false));
if (verbose)
result1->Set("diagnostic_information", DiagnosticInformation(ex));
} }
results.push_back(std::move(result1)); results.push_back(std::move(result1));

View File

@ -129,7 +129,7 @@ bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& re
uattrs = params->Get("attrs"); uattrs = params->Get("attrs");
} catch (const std::exception&) { } catch (const std::exception&) {
HttpUtility::SendJsonError(response, params, 400, HttpUtility::SendJsonError(response, params, 400,
"Invalid type for 'attrs' attribute specified. Array type is required.", Empty); "Invalid type for 'attrs' attribute specified. Array type is required.");
return true; return true;
} }
@ -137,7 +137,7 @@ bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& re
ujoins = params->Get("joins"); ujoins = params->Get("joins");
} catch (const std::exception&) { } catch (const std::exception&) {
HttpUtility::SendJsonError(response, params, 400, HttpUtility::SendJsonError(response, params, 400,
"Invalid type for 'joins' attribute specified. Array type is required.", Empty); "Invalid type for 'joins' attribute specified. Array type is required.");
return true; return true;
} }
@ -145,7 +145,7 @@ bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& re
umetas = params->Get("meta"); umetas = params->Get("meta");
} catch (const std::exception&) { } catch (const std::exception&) {
HttpUtility::SendJsonError(response, params, 400, HttpUtility::SendJsonError(response, params, 400,
"Invalid type for 'meta' attribute specified. Array type is required.", Empty); "Invalid type for 'meta' attribute specified. Array type is required.");
return true; return true;
} }
@ -166,7 +166,7 @@ bool ObjectQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& re
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 404, HttpUtility::SendJsonError(response, params, 404,
"No objects found.", "No objects found.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return true; return true;
} }

View File

@ -104,7 +104,7 @@ bool StatusHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 404, HttpUtility::SendJsonError(response, params, 404,
"No objects found.", "No objects found.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return true; return true;
} }

View File

@ -127,7 +127,7 @@ bool TemplateQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest&
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 404, HttpUtility::SendJsonError(response, params, 404,
"No templates found.", "No templates found.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return true; return true;
} }

View File

@ -91,7 +91,7 @@ bool TypeQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& requ
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 404, HttpUtility::SendJsonError(response, params, 404,
"No objects found.", "No objects found.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return true; return true;
} }

View File

@ -97,7 +97,7 @@ bool VariableQueryHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest&
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 404, HttpUtility::SendJsonError(response, params, 404,
"No variables found.", "No variables found.",
HttpUtility::GetLastParameter(params, "verboseErrors") ? DiagnosticInformation(ex) : ""); DiagnosticInformation(ex));
return true; return true;
} }