Skip to content

Commit

Permalink
Add package parameter in object creation API
Browse files Browse the repository at this point in the history
Allow deletion via API of objects belonging to any package

refs Icinga#8639
  • Loading branch information
Mattia Codato committed Aug 27, 2021
1 parent 95cdc00 commit 3d953ea
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 68 deletions.
44 changes: 18 additions & 26 deletions lib/remote/apilistener-configsync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin

bool newObject = false;

String package = "_api";
if (params->Contains("package"))
package = params->Get("package");

if (!object && !config.IsEmpty()) {
newObject = true;

Expand All @@ -120,7 +124,8 @@ Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin
* Create the config object through our internal API.
* IMPORTANT: Pass the origin to prevent cluster sync loops.
*/
if (!ConfigObjectUtility::CreateObject(ptype, objName, config, errors, nullptr, origin)) {
if (!ConfigObjectUtility::CreateObject(ptype, objName, config, errors, nullptr, origin,
package)) {
Log(LogCritical, "ApiListener")
<< "Could not create object '" << objName << "':";

Expand Down Expand Up @@ -266,12 +271,6 @@ Value ApiListener::ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin
return Empty;
}

if (object->GetPackage() != "_api") {
Log(LogCritical, "ApiListener")
<< "Could not delete object '" << objName << "': Not created by the API.";
return Empty;
}

Log(LogNotice, "ApiListener")
<< "Processing config delete"
<< " from '" << identity << "' (endpoint: '" << endpoint->GetName() << "', zone: '" << endpointZone->GetName() << "')"
Expand Down Expand Up @@ -311,9 +310,6 @@ void ApiListener::UpdateConfigObject(const ConfigObject::Ptr& object, const Mess
}
}

if (object->GetPackage() != "_api" && object->GetVersion() == 0)
return;

Dictionary::Ptr params = new Dictionary();

Dictionary::Ptr message = new Dictionary({
Expand All @@ -325,27 +321,26 @@ void ApiListener::UpdateConfigObject(const ConfigObject::Ptr& object, const Mess
params->Set("name", object->GetName());
params->Set("type", object->GetReflectionType()->GetName());
params->Set("version", object->GetVersion());
params->Set("package", object->GetPackage());

String zoneName = object->GetZoneName();

if (!zoneName.IsEmpty())
params->Set("zone", zoneName);

if (object->GetPackage() == "_api") {
String file;

try {
file = ConfigObjectUtility::GetObjectConfigPath(object->GetReflectionType(), object->GetName());
} catch (const std::exception& ex) {
Log(LogNotice, "ApiListener")
<< "Cannot sync object '" << object->GetName() << "': " << ex.what();
return;
}
String file;

std::ifstream fp(file.CStr(), std::ifstream::binary);
if (!fp)
return;
try {
file = ConfigObjectUtility::GetObjectConfigPath(object->GetReflectionType(), object->GetName(),
object->GetPackage());
} catch (const std::exception& ex) {
Log(LogNotice, "ApiListener")
<< "Cannot sync object '" << object->GetName() << "': " << ex.what();
return;
}

std::ifstream fp(file.CStr(), std::ifstream::binary);
if (fp) {
String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
params->Set("config", content);
}
Expand Down Expand Up @@ -396,9 +391,6 @@ void ApiListener::UpdateConfigObject(const ConfigObject::Ptr& object, const Mess
void ApiListener::DeleteConfigObject(const ConfigObject::Ptr& object, const MessageOrigin::Ptr& origin,
const JsonRpcConnection::Ptr& client)
{
if (object->GetPackage() != "_api")
return;

/* only send objects to zones which have access to the object */
if (client) {
Zone::Ptr target_zone = client->GetEndpoint()->GetZone();
Expand Down
40 changes: 18 additions & 22 deletions lib/remote/configobjectutility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,24 @@

using namespace icinga;

String ConfigObjectUtility::GetConfigDir()
String ConfigObjectUtility::GetConfigDir(const String& package)
{
String prefix = ConfigPackageUtility::GetPackageDir() + "/_api/";
String activeStage = ConfigPackageUtility::GetActiveStage("_api");
String prefix = ConfigPackageUtility::GetPackageDir() + "/" + package + "/";
String activeStage = ConfigPackageUtility::GetActiveStage(package);

if (activeStage.IsEmpty())
RepairPackage("_api");
RepairPackage(package);

return prefix + activeStage;
}

String ConfigObjectUtility::GetObjectConfigPath(const Type::Ptr& type, const String& fullName)
String ConfigObjectUtility::GetObjectConfigPath(const Type::Ptr& type, const String& fullName, const String& package)
{
String typeDir = type->GetPluralName();
boost::algorithm::to_lower(typeDir);

/* This may throw an exception the caller above must handle. */
String prefix = GetConfigDir() + "/conf.d/" + type->GetPluralName().ToLower() + "/";
String prefix = GetConfigDir(package) + "/conf.d/" + type->GetPluralName().ToLower() + "/";

String escapedName = EscapeName(fullName);

Expand Down Expand Up @@ -98,13 +98,10 @@ void ConfigObjectUtility::RepairPackage(const String& package)
}
}

void ConfigObjectUtility::CreateStorage()
void ConfigObjectUtility::CreateStorage(const String& package)
{
std::unique_lock<std::mutex> lock(ConfigPackageUtility::GetStaticPackageMutex());

/* For now, we only use _api as our creation target. */
String package = "_api";

if (!ConfigPackageUtility::PackageExists(package)) {
Log(LogNotice, "ConfigObjectUtility")
<< "Package " << package << " doesn't exist yet, creating it.";
Expand Down Expand Up @@ -169,9 +166,10 @@ String ConfigObjectUtility::CreateObjectConfig(const Type::Ptr& type, const Stri
}

bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& fullName,
const String& config, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation, const Value& cookie)
const String& config, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation, const Value& cookie,
const String& package)
{
CreateStorage();
CreateStorage(package);

{
auto configType (dynamic_cast<ConfigType*>(type.get()));
Expand All @@ -185,7 +183,7 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full
String path;

try {
path = GetObjectConfigPath(type, fullName);
path = GetObjectConfigPath(type, fullName, package);
} catch (const std::exception& ex) {
errors->Add("Config package broken: " + DiagnosticInformation(ex, false));
return false;
Expand All @@ -197,7 +195,7 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full
fp << config;
fp.close();

std::unique_ptr<Expression> expr = ConfigCompiler::CompileFile(path, String(), "_api");
std::unique_ptr<Expression> expr = ConfigCompiler::CompileFile(path, String(), package);

try {
ActivationScope ascope;
Expand Down Expand Up @@ -349,12 +347,17 @@ bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bo
String path;

try {
path = GetObjectConfigPath(object->GetReflectionType(), name);
path = GetObjectConfigPath(object->GetReflectionType(), name, object->GetPackage());
} catch (const std::exception& ex) {
errors->Add("Config package broken: " + DiagnosticInformation(ex, false));
return false;
}

/*
* This is currently not throwing any error if the config file doesn't exist.
* This can happen if it is called for an object that was not created by the API.
* https://github.com/Icinga/icinga2/issues/8639
*/
Utility::Remove(path);

Log(LogInformation, "ConfigObjectUtility")
Expand All @@ -366,12 +369,5 @@ bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bo
bool ConfigObjectUtility::DeleteObject(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors,
const Array::Ptr& diagnosticInformation, const Value& cookie)
{
if (object->GetPackage() != "_api") {
if (errors)
errors->Add("Object cannot be deleted because it was not created using the API.");

return false;
}

return DeleteObjectHelper(object, cascade, errors, diagnosticInformation, cookie);
}
9 changes: 5 additions & 4 deletions lib/remote/configobjectutility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@ class ConfigObjectUtility
{

public:
static String GetConfigDir();
static String GetObjectConfigPath(const Type::Ptr& type, const String& fullName);
static String GetConfigDir(const String &package = "_api");
static String GetObjectConfigPath(const Type::Ptr& type, const String& fullName, const String &package = "_api");
static void RepairPackage(const String& package);
static void CreateStorage();
static void CreateStorage(const String& package = "_api");

static String CreateObjectConfig(const Type::Ptr& type, const String& fullName,
bool ignoreOnError, const Array::Ptr& templates, const Dictionary::Ptr& attrs);

static bool CreateObject(const Type::Ptr& type, const String& fullName,
const String& config, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation, const Value& cookie = Empty);
const String& config, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation, const Value& cookie = Empty,
const String& package = "_api");

static bool DeleteObject(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors,
const Array::Ptr& diagnosticInformation, const Value& cookie = Empty);
Expand Down
12 changes: 11 additions & 1 deletion lib/remote/createobjecthandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ bool CreateObjectHandler::HandleRequest(
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");

String package = "_api";
if (params->Contains("package")) {
package = HttpUtility::GetLastParameter(params, "package");

if (!ConfigPackageUtility::ValidatePackageName(package) || package.GetData().at(0) == '_') {
HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + package + "'.");
return true;
}
}

/* Object creation can cause multiple errors and optionally diagnostic information.
* We can't use SendJsonError() here.
*/
Expand All @@ -116,7 +126,7 @@ bool CreateObjectHandler::HandleRequest(
return true;
}

if (!ConfigObjectUtility::CreateObject(type, name, config, errors, diagnosticInformation)) {
if (!ConfigObjectUtility::CreateObject(type, name, config, errors, diagnosticInformation, nullptr, package)) {
result1->Set("errors", errors);
result1->Set("code", 500);
result1->Set("status", "Object could not be created.");
Expand Down
17 changes: 16 additions & 1 deletion lib/remote/deleteobjecthandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ bool DeleteObjectHandler::HandleRequest(

bool cascade = HttpUtility::GetLastParameter(params, "cascade");
bool verbose = HttpUtility::GetLastParameter(params, "verbose");
String package = HttpUtility::GetLastParameter(params, "package");

if (package == Empty) {
package = "_api";
} else {
if (!ConfigPackageUtility::ValidatePackageName(package) || package.GetData().at(0) == '_') {
HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + package + "'.");
return true;
}
}

ArrayData results;

Expand All @@ -76,7 +86,12 @@ bool DeleteObjectHandler::HandleRequest(
Array::Ptr errors = new Array();
Array::Ptr diagnosticInformation = new Array();

if (!ConfigObjectUtility::DeleteObject(obj, cascade, errors, diagnosticInformation)) {
if (params->Contains("package") && obj->GetPackage() != package) {
code = 500;
status = "Object could not be deleted because it was create in package '" + obj->GetPackage() +
"' and not in package: '" + package + "'.";
success = false;
} else if (!ConfigObjectUtility::DeleteObject(obj, cascade, errors, diagnosticInformation)) {
code = 500;
status = "Object could not be deleted.";
success = false;
Expand Down
44 changes: 30 additions & 14 deletions lib/remote/modifyobjecthandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ bool ModifyObjectHandler::HandleRequest(
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");

String package = "_api";
if (params->Contains("package")) {
package = HttpUtility::GetLastParameter(params, "package");

if (!ConfigPackageUtility::ValidatePackageName(package) || package.GetData().at(0) == '_') {
HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + package + "'.");
return true;
}
}

ArrayData results;

for (const ConfigObject::Ptr& obj : objs) {
Expand All @@ -87,23 +97,29 @@ bool ModifyObjectHandler::HandleRequest(

String key;

try {
if (attrs) {
ObjectLock olock(attrs);
for (const Dictionary::Pair& kv : attrs) {
key = kv.first;
obj->ModifyAttribute(kv.first, kv.second);
if (params->Contains("package") && obj->GetPackage() != package) {
result1->Set("code", 500);
result1->Set("status", "Object cannot be modified because it was create in package '" +
obj->GetPackage() + "' and not in package: '" + package + "'.");
} else {
try {
if (attrs) {
ObjectLock olock(attrs);
for (const Dictionary::Pair &kv : attrs) {
key = kv.first;
obj->ModifyAttribute(kv.first, kv.second);
}
}
}

result1->Set("code", 200);
result1->Set("status", "Attributes updated.");
} catch (const std::exception& ex) {
result1->Set("code", 500);
result1->Set("status", "Attribute '" + key + "' could not be set: " + DiagnosticInformation(ex, false));
result1->Set("code", 200);
result1->Set("status", "Attributes updated.");
} catch (const std::exception &ex) {
result1->Set("code", 500);
result1->Set("status", "Attribute '" + key + "' could not be set: " + DiagnosticInformation(ex, false));

if (verbose)
result1->Set("diagnostic_information", DiagnosticInformation(ex));
if (verbose)
result1->Set("diagnostic_information", DiagnosticInformation(ex));
}
}

results.push_back(std::move(result1));
Expand Down

0 comments on commit 3d953ea

Please sign in to comment.