#include "cli/repositoryutility.hpp"
#include "cli/clicommand.hpp"
+#include "config/configtype.hpp"
+#include "config/configcompilercontext.hpp"
+#include "config/configcompiler.hpp"
#include "base/logger.hpp"
#include "base/application.hpp"
#include "base/convert.hpp"
#include "base/json.hpp"
#include "base/netstring.hpp"
+#include "base/tlsutility.hpp"
#include "base/stdiostream.hpp"
#include "base/debug.hpp"
#include "base/objectlock.hpp"
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/regex.hpp>
#include <fstream>
#include <iostream>
Dictionary::Ptr RepositoryUtility::GetArgumentAttributes(const std::vector<std::string>& arguments)
{
- Dictionary::Ptr attr = make_shared<Dictionary>();
+ Dictionary::Ptr attrs = make_shared<Dictionary>();
BOOST_FOREACH(const String& kv, arguments) {
std::vector<String> tokens;
boost::algorithm::split(tokens, kv, boost::is_any_of("="));
- if (tokens.size() == 2) {
- attr->Set(tokens[0], tokens[1]);
- } else
+ if (tokens.size() != 2) {
Log(LogWarning, "cli")
<< "Cannot parse passed attributes: " << boost::algorithm::join(tokens, "=");
+ continue;
+ }
+
+ Value value;
+
+ try {
+ value = Convert::ToDouble(tokens[1]);
+ } catch (...) {
+ value = tokens[1];
+ }
+
+ attrs->Set(tokens[0], value);
}
- return attr;
+ return attrs;
}
String RepositoryUtility::GetRepositoryConfigPath(void)
{
String path = GetRepositoryConfigPath() + "/";
- if (type == "Host") {
+ if (type == "Host")
path += "hosts";
- }
else if (type == "Service")
path += "hosts/" + object->Get("host_name");
else if (type == "Zone")
path += "zones";
- else if (type == "Endpoints")
+ else if (type == "Endpoint")
path += "endpoints";
return path;
}
+bool RepositoryUtility::FilterRepositoryObjects(const String& type, const String& path)
+{
+ if (type == "Host") {
+ boost::regex expr("hosts/[^/]*.conf", boost::regex::icase);
+ boost::smatch what;
+ return boost::regex_search(path.GetData(), what, expr);
+ }
+ else if (type == "Service")
+ return Utility::Match("*hosts/*/*.conf", path);
+ else if (type == "Zone")
+ return Utility::Match("*zones/*.conf", path);
+ else if (type == "Endpoints")
+ return Utility::Match("*endpoints/*.conf", path);
+
+ return false;
+}
+
String RepositoryUtility::GetRepositoryObjectConfigFilePath(const String& type, const Dictionary::Ptr& object)
{
String path = GetRepositoryObjectConfigPath(type, object);
return Application::GetLocalStateDir() + "/lib/icinga2/repository/changes";
}
+/* printers */
void RepositoryUtility::PrintObjects(std::ostream& fp, const String& type)
{
std::vector<String> objects = GetObjects(); //full path
BOOST_FOREACH(const String& object, objects) {
- Dictionary::Ptr obj = GetObjectFromRepository(object);
+ if (!FilterRepositoryObjects(type, object)) {
+ Log(LogDebug, "cli")
+ << "Ignoring object '" << object << "'. Type '" << type << "' does not match.";
+ continue;
+ }
+
+ String file = Utility::BaseName(object);
+ boost::algorithm::replace_all(file, ".conf", "");
+
+ fp << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << type << ConsoleColorTag(Console_Normal)
+ << " '" << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << file << ConsoleColorTag(Console_Normal) << "'";
+
+ String prefix = Utility::DirName(object);
+
+ if (type == "Service") {
+ std::vector<String> tokens;
+ boost::algorithm::split(tokens, prefix, boost::is_any_of("/"));
+
+ String host_name = tokens[tokens.size()-1];
+ fp << " (on " << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << "Host" << ConsoleColorTag(Console_Normal)
+ << " '" << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << host_name << ConsoleColorTag(Console_Normal) << "')";
- if (obj) {
- fp << "Object Name: " << object << "\n";
- fp << JsonEncode(obj);
}
+
+ fp << "\n";
+
+ /*
+ Dictionary::Ptr obj = GetObjectFromRepository(object); //TODO: config parser not implemented yet!
+
+ if (obj)
+ fp << JsonEncode(obj);
+ */
}
}
-/* public interface, only logs changes */
-bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attr)
+void RepositoryUtility::PrintChangeLog(std::ostream& fp)
+{
+ Array::Ptr changelog = make_shared<Array>();
+
+ GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
+
+ ObjectLock olock(changelog);
+
+ std::cout << "Changes to be committed:\n\n";
+
+ BOOST_FOREACH(const Value& entry, changelog) {
+ FormatChangelogEntry(std::cout, entry);
+ }
+}
+
+class RepositoryTypeRuleUtilities : public TypeRuleUtilities
+{
+public:
+ virtual bool ValidateName(const String& type, const String& name, String *hint) const
+ {
+ return true;
+ }
+};
+
+/* modify objects and write changelog */
+bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attrs)
{
/* add a new changelog entry by timestamp */
- String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
+ String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(Utility::GetTime()) + "-" + type + "-" + SHA256(name) + ".change";
Dictionary::Ptr change = make_shared<Dictionary>();
change->Set("name", name);
change->Set("type", type);
change->Set("command", "add");
- change->Set("attr", attr);
+ change->Set("attrs", attrs);
+
+ ConfigCompilerContext::GetInstance()->Reset();
+
+ String fname, fragment;
+ BOOST_FOREACH(boost::tie(fname, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) {
+ ConfigCompiler::CompileText(fname, fragment);
+ }
+
+ ConfigType::Ptr ctype = ConfigType::GetByName(type);
+
+ if (!ctype)
+ Log(LogCritical, "cli")
+ << "No validation type available for '" << type << "'.";
+ else {
+ Dictionary::Ptr vattrs = attrs->ShallowClone();
+ vattrs->Set("__name", vattrs->Get("name"));
+ vattrs->Remove("name");
+ vattrs->Remove("import");
+ vattrs->Set("type", type);
+
+ RepositoryTypeRuleUtilities utils;
+ ctype->ValidateItem(name, vattrs, DebugInfo(), &utils);
+
+ int warnings = 0, errors = 0;
+
+ BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
+ String logmsg = String("Config ") + (message.Error ? "error" : "warning") + ": " + message.Text;
+
+ if (message.Error) {
+ Log(LogCritical, "config", logmsg);
+ errors++;
+ } else {
+ Log(LogWarning, "config", logmsg);
+ warnings++;
+ }
+ }
+
+ if (errors > 0)
+ return false;
+ }
return WriteObjectToRepositoryChangeLog(path, change);
}
-bool RepositoryUtility::RemoveObject(const String& name, const String& type, const Dictionary::Ptr& attr)
+bool RepositoryUtility::RemoveObject(const String& name, const String& type, const Dictionary::Ptr& attrs)
{
/* add a new changelog entry by timestamp */
- String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
+ String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(Utility::GetTime()) + "-" + type + "-" + SHA256(name) + ".change";
Dictionary::Ptr change = make_shared<Dictionary>();
change->Set("name", name);
change->Set("type", type);
change->Set("command", "remove");
- change->Set("attr", attr); //required for service->host_name
+ change->Set("attrs", attrs); //required for service->host_name
return WriteObjectToRepositoryChangeLog(path, change);
}
-bool RepositoryUtility::CommitChangeLog(void)
+bool RepositoryUtility::SetObjectAttribute(const String& name, const String& type, const String& attr, const Value& val)
{
- GetChangeLog(boost::bind(RepositoryUtility::CommitChange, _1));
+ //TODO: Implement modification commands
+ return true;
+}
+
+bool RepositoryUtility::ClearChangeLog(void)
+{
+ GetChangeLog(boost::bind(RepositoryUtility::ClearChange, _1, _2));
return true;
}
-void RepositoryUtility::PrintChangeLog(std::ostream& fp)
+bool RepositoryUtility::ChangeLogHasPendingChanges(void)
{
Array::Ptr changelog = make_shared<Array>();
-
GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
- ObjectLock olock(changelog);
+ return changelog->GetLength() > 0;
+}
- std::cout << "Changes to be committed:\n";
+/* commit changelog */
+bool RepositoryUtility::CommitChangeLog(void)
+{
+ GetChangeLog(boost::bind(RepositoryUtility::CommitChange, _1, _2));
- BOOST_FOREACH(const Value& entry, changelog) {
- std::cout << JsonEncode(entry) << "\n"; //TODO better formatting
- }
+ return true;
}
-bool RepositoryUtility::SetObjectAttribute(const String& name, const String& type, const String& attr, const Value& val)
+/* write/read from changelog repository */
+bool RepositoryUtility::WriteObjectToRepositoryChangeLog(const String& path, const Dictionary::Ptr& item)
{
- //TODO: Implement modification commands
+ Log(LogInformation, "cli", "Dumping changelog items to file '" + path + "'");
+
+ Utility::MkDirP(Utility::DirName(path), 0750);
+
+ String tempPath = path + ".tmp";
+
+ std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
+ fp << JsonEncode(item);
+ fp.close();
+
+#ifdef _WIN32
+ _unlink(path.CStr());
+#endif /* _WIN32 */
+
+ if (rename(tempPath.CStr(), path.CStr()) < 0) {
+ BOOST_THROW_EXCEPTION(posix_error()
+ << boost::errinfo_api_function("rename")
+ << boost::errinfo_errno(errno)
+ << boost::errinfo_file_name(tempPath));
+ }
+
return true;
}
+Dictionary::Ptr RepositoryUtility::GetObjectFromRepositoryChangeLog(const String& filename)
+{
+ std::fstream fp;
+ fp.open(filename.CStr(), std::ifstream::in);
+
+ if (!fp)
+ return Dictionary::Ptr();
+
+ String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
+
+ fp.close();
+
+ return JsonDecode(content);
+}
+
/* internal implementation when changes are committed */
-bool RepositoryUtility::AddObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
+bool RepositoryUtility::AddObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attrs)
{
- String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
+ String path = GetRepositoryObjectConfigPath(type, attrs) + "/" + name + ".conf";
- return WriteObjectToRepository(path, name, type, attr);
+ return WriteObjectToRepository(path, name, type, attrs);
}
-bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
+bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attrs)
{
- String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
+ String path = GetRepositoryObjectConfigPath(type, attrs) + "/" + name + ".conf";
+ bool success = RemoveObjectFileInternal(path);
+
+ /* special treatment for hosts -> remove the services too */
+ if (type == "Host") {
+ path = GetRepositoryObjectConfigPath(type, attrs) + "/" + name;
+
+ std::vector<String> files;
+ Utility::GlobRecursive(path, "*.conf",
+ boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(files)), GlobFile);
- return RemoveObjectFileInternal(path);
+ BOOST_FOREACH(const String& file, files) {
+ RemoveObjectFileInternal(file);
+ }
+#ifndef _WIN32
+ rmdir(path.CStr());
+#else
+ _rmdir(path.CStr());
+#endif /* _WIN32 */
+
+ }
+
+ return success;
}
bool RepositoryUtility::RemoveObjectFileInternal(const String& path)
}
if (unlink(path.CStr()) < 0) {
- Log(LogCritical, "cli", "Cannot remove file '" + path +
+ Log(LogCritical, "cli", "Cannot remove path '" + path +
"'. Failed with error code " + Convert::ToString(errno) + ", \"" + Utility::FormatErrorNumber(errno) + "\".");
return false;
}
return true;
}
-bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& key, const Value& val, const Dictionary::Ptr& attr)
+bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& key, const Value& val, const Dictionary::Ptr& attrs)
{
//Fixme
- String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
+ String path = GetRepositoryObjectConfigPath(type, attrs) + "/" + name + ".conf";
- Dictionary::Ptr obj = GetObjectFromRepository(path);
+ Dictionary::Ptr obj = GetObjectFromRepository(path); //TODO
if (!obj) {
Log(LogCritical, "cli")
bool RepositoryUtility::WriteObjectToRepository(const String& path, const String& name, const String& type, const Dictionary::Ptr& item)
{
- Log(LogInformation, "cli", "Dumping config items to file '" + path + "'");
+ Log(LogInformation, "cli")
+ << "Dumping config object '" << name << "' to file '" << path << "'";
Utility::MkDirP(Utility::DirName(path), 0755);
return Dictionary::Ptr();
}
-bool RepositoryUtility::WriteObjectToRepositoryChangeLog(const String& path, const Dictionary::Ptr& item)
-{
- Log(LogInformation, "cli", "Dumping changelog items to file '" + path + "'");
-
- Utility::MkDirP(Utility::DirName(path), 0750);
-
- String tempPath = path + ".tmp";
-
- std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
- fp << JsonEncode(item);
- fp.close();
-
-#ifdef _WIN32
- _unlink(path.CStr());
-#endif /* _WIN32 */
-
- if (rename(tempPath.CStr(), path.CStr()) < 0) {
- BOOST_THROW_EXCEPTION(posix_error()
- << boost::errinfo_api_function("rename")
- << boost::errinfo_errno(errno)
- << boost::errinfo_file_name(tempPath));
- }
-
- return true;
-}
-
-Dictionary::Ptr RepositoryUtility::GetObjectFromRepositoryChangeLog(const String& filename)
-{
- std::fstream fp;
- fp.open(filename.CStr(), std::ifstream::in);
-
- if (!fp)
- return Dictionary::Ptr();
-
- String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
-
- fp.close();
-
- return JsonDecode(content);
-}
/*
* collect functions
std::vector<String> RepositoryUtility::GetObjects(void)
{
std::vector<String> objects;
- String path = GetRepositoryConfigPath() + "/";
+ String path = GetRepositoryConfigPath();
- Utility::Glob(path + "/*.conf",
+ Utility::GlobRecursive(path, "*.conf",
boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile);
return objects;
}
-bool RepositoryUtility::GetChangeLog(const boost::function<void (const Dictionary::Ptr&)>& callback)
+bool RepositoryUtility::GetChangeLog(const boost::function<void (const Dictionary::Ptr&, const String&)>& callback)
{
std::vector<String> changelog;
String path = GetRepositoryChangeLogPath() + "/";
std::sort(changelog.begin(), changelog.end());
BOOST_FOREACH(const String& entry, changelog) {
- Dictionary::Ptr change = GetObjectFromRepositoryChangeLog(path + entry + ".change");
+ String file = path + entry + ".change";
+ Dictionary::Ptr change = GetObjectFromRepositoryChangeLog(file);
- Log(LogInformation, "cli")
+ Log(LogDebug, "cli")
<< "Collecting entry " << entry << "\n";
if (change)
- callback(change);
+ callback(change, file);
}
return true;
String file = Utility::BaseName(change_file);
boost::algorithm::replace_all(file, ".change", "");
- Log(LogDebug, "cli", "Adding change file: " + file);
+ Log(LogDebug, "cli")
+ << "Adding change file: '" << file << "'.";
+
changelog.push_back(file);
}
-void RepositoryUtility::CommitChange(const Dictionary::Ptr& change)
+void RepositoryUtility::CollectChange(const Dictionary::Ptr& change, Array::Ptr& changes)
{
- Log(LogInformation, "cli")
+ changes->Add(change);
+}
+
+/*
+ * Commit Changelog entry
+ */
+void RepositoryUtility::CommitChange(const Dictionary::Ptr& change, const String& path)
+{
+ Log(LogDebug, "cli")
<< "Got change " << change->Get("name");
String name = change->Get("name");
String type = change->Get("type");
String command = change->Get("command");
- Dictionary::Ptr attr;
+ Dictionary::Ptr attrs;
- if (change->Contains("attr")) {
- attr = change->Get("attr");
+ if (change->Contains("attrs")) {
+ attrs = change->Get("attrs");
}
+ bool success = false;
+
if (command == "add") {
- AddObjectInternal(name, type, attr);
+ success = AddObjectInternal(name, type, attrs);
}
else if (command == "remove") {
- RemoveObjectInternal(name, type, attr);
+ success = RemoveObjectInternal(name, type, attrs);
+ }
+
+ if (success) {
+ Log(LogNotice, "cli")
+ << "Removing changelog file '" << path << "'.";
+ RemoveObjectFileInternal(path);
}
}
-void RepositoryUtility::CollectChange(const Dictionary::Ptr& change, Array::Ptr& changes)
+/*
+ * Clear Changelog entry
+ */
+void RepositoryUtility::ClearChange(const Dictionary::Ptr& change, const String& path)
{
- changes->Add(change);
+ Log(LogDebug, "cli")
+ << "Clearing change " << change->Get("name");
+
+ Log(LogInformation, "cli")
+ << "Removing changelog file '" << path << "'.";
+
+ RemoveObjectFileInternal(path);
}
+/*
+ * Print Changelog helpers
+ */
+void RepositoryUtility::FormatChangelogEntry(std::ostream& fp, const Dictionary::Ptr& change)
+{
+ if (!change)
+ return;
+
+ if (change->Get("command") == "add")
+ fp << "Adding";
+ if (change->Get("command") == "remove")
+ fp << "Removing";
+
+ String type = change->Get("type");
+ boost::algorithm::to_lower(type);
+ Dictionary::Ptr attrs = change->Get("attrs");
+
+ fp << " " << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << type << ConsoleColorTag(Console_Normal) << " '";
+ fp << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << change->Get("name") << ConsoleColorTag(Console_Normal) << "'";
+
+ if (!attrs || attrs->GetLength() == 0) {
+ fp << "\n";
+ return;
+ }
+
+ fp << " with attributes: \n";
+
+ BOOST_FOREACH(const Dictionary::Pair& kv, attrs) {
+ /* skip the name */
+ if (kv.first == "name")
+ continue;
+
+ fp << std::setw(4) << " " << ConsoleColorTag(Console_ForegroundGreen) << kv.first << ConsoleColorTag(Console_Normal) << " = ";
+ FormatValue(fp, kv.second);
+ fp << "\n";
+ }
+}
/*
* print helpers for configuration