]> granicus.if.org Git - icinga2/blobdiff - lib/cli/repositoryutility.cpp
Cli: Ignore 'import' attribute on repository add validation
[icinga2] / lib / cli / repositoryutility.cpp
index 70177f1f7d9d174fee92a7f02b2a3ea8cd08d388..88936266fc54e94d4cbd150f403d28bbe7c73f13 100644 (file)
 
 #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/serializer.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/foreach.hpp>
 #include <boost/algorithm/string/join.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>
 
 using namespace icinga;
 
-String RepositoryUtility::GetRepositoryDPath(void)
+Dictionary::Ptr RepositoryUtility::GetArgumentAttributes(const std::vector<std::string>& arguments)
+{
+       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) {
+                       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 attrs;
+}
+
+String RepositoryUtility::GetRepositoryConfigPath(void)
 {
        return Application::GetSysconfDir() + "/icinga2/repository.d";
 }
 
-String RepositoryUtility::GetRepositoryDObjectsPath(const String& type, const String& hostname)
+String RepositoryUtility::GetRepositoryObjectConfigPath(const String& type, const Dictionary::Ptr& object)
 {
+       String path = GetRepositoryConfigPath() + "/";
+
        if (type == "Host")
-               return GetRepositoryDPath() + "/hosts";
+               path += "hosts";
        else if (type == "Service")
-               return GetRepositoryDPath() + "/hosts/" + hostname;
+               path += "hosts/" + object->Get("host_name");
        else if (type == "Zone")
-               return GetRepositoryDPath() + "/zones";
+               path += "zones";
+       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 GetRepositoryDPath() + "/endpoints";
-       else
-               return GetRepositoryDPath();
+               return Utility::Match("*endpoints/*.conf", path);
+
+       return false;
+}
+
+String RepositoryUtility::GetRepositoryObjectConfigFilePath(const String& type, const Dictionary::Ptr& object)
+{
+       String path = GetRepositoryObjectConfigPath(type, object);
+
+       path += "/" + object->Get("name") + ".conf";
+
+       return path;
 }
 
 String RepositoryUtility::GetRepositoryChangeLogPath(void)
 {
-       return Application::GetLocalStateDir() + "/lib/icinga2/repository";
+       return Application::GetLocalStateDir() + "/lib/icinga2/repository/changes";
 }
 
+/* printers */
 void RepositoryUtility::PrintObjects(std::ostream& fp, const String& type)
 {
-       std::vector<String> objects;
-       GetObjects(type, objects);
+       std::vector<String> objects = GetObjects(); //full path
 
        BOOST_FOREACH(const String& object, objects) {
-               Dictionary::Ptr obj = GetObjectFromRepository(GetRepositoryDObjectsPath(type) + "/" + object + ".conf");
+               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 << JsonSerialize(obj);
                }
+
+               fp << "\n";
+
+               /*
+               Dictionary::Ptr obj = GetObjectFromRepository(object); //TODO: config parser not implemented yet!
+
+               if (obj)
+                       fp << JsonEncode(obj);
+               */
+       }
+}
+
+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);
        }
 }
 
-/* public interface, only logs changes */
-bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attr)
+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>();
 
@@ -87,15 +201,55 @@ bool RepositoryUtility::AddObject(const String& name, const String& type, const
        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)
+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>();
 
@@ -103,51 +257,115 @@ bool RepositoryUtility::RemoveObject(const String& name, const String& type)
        change->Set("name", name);
        change->Set("type", type);
        change->Set("command", "remove");
+       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 << JsonSerialize(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 = GetRepositoryDObjectsPath(type, name) + "/" + 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)
+bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attrs)
 {
-       String path = GetRepositoryDObjectsPath(type, name) + "/" + 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;
 
-       return RemoveObjectFileInternal(path);
+               std::vector<String> files;
+               Utility::GlobRecursive(path, "*.conf",
+                   boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(files)), GlobFile);
+
+               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)
@@ -158,7 +376,7 @@ 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;
        }
@@ -166,12 +384,12 @@ bool RepositoryUtility::RemoveObjectFileInternal(const String& path)
        return true;
 }
 
-bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& attr, const Value& val)
+bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& key, const Value& val, const Dictionary::Ptr& attrs)
 {
        //Fixme
-       String path = GetRepositoryDObjectsPath(type, name) + "/" + 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")
@@ -179,7 +397,7 @@ bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const Str
                return false;
        }
 
-       obj->Set(attr, val);
+       obj->Set(key, val);
 
        std::cout << "Writing object '" << name << "' to path '" << path << "'.\n";
 
@@ -195,7 +413,8 @@ bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const Str
 
 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);
 
@@ -226,101 +445,50 @@ Dictionary::Ptr RepositoryUtility::GetObjectFromRepository(const String& filenam
        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), 0755);
-
-       String tempPath = path + ".tmp";
-
-        std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
-        fp << JsonSerialize(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 JsonDeserialize(content);
-}
-
 
 /*
  * collect functions
  */
-bool RepositoryUtility::GetObjects(const String& type, std::vector<String>& objects)
+std::vector<String> RepositoryUtility::GetObjects(void)
 {
-       String path = GetRepositoryDPath() + "/";
+       std::vector<String> objects;
+       String path = GetRepositoryConfigPath();
 
-       if (type == "service")
-               path = "hosts/*";
-       else
-               path = type;
+       Utility::GlobRecursive(path, "*.conf",
+           boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile);
 
-       if (!Utility::Glob(path + "/*.conf",
-           boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile)) {
-               Log(LogCritical, "cli", "Cannot access path '" + path + "'.");
-               return false;
-       }
-
-       return true;
+       return objects;
 }
 
 void RepositoryUtility::CollectObjects(const String& object_file, std::vector<String>& objects)
 {
-       String object = Utility::BaseName(object_file);
-       boost::algorithm::replace_all(object, ".conf", "");
+       Log(LogDebug, "cli")
+           << "Adding object: '" << object_file << "'.";
 
-       Log(LogDebug, "cli", "Adding object: " + object);
-       objects.push_back(object);
+       objects.push_back(object_file);
 }
 
 
-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() + "/";
 
-       if (!Utility::Glob(path + "/*.change",
-           boost::bind(&RepositoryUtility::CollectChangeLog, _1, boost::ref(changelog)), GlobFile)) {
-               Log(LogCritical, "cli", "Cannot access path '" + path + "'.");
-               return false;
-       }
+       Utility::Glob(path + "/*.change",
+           boost::bind(&RepositoryUtility::CollectChangeLog, _1, boost::ref(changelog)), GlobFile);
 
        /* sort by timestamp ascending */
        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;
@@ -331,37 +499,101 @@ void RepositoryUtility::CollectChangeLog(const String& change_file, std::vector<
        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)
+{
+       changes->Add(change);
+}
+
+/*
+ * Commit Changelog entry
+ */
+void RepositoryUtility::CommitChange(const Dictionary::Ptr& change, const String& path)
 {
-       Log(LogInformation, "cli")
+       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);
+               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
@@ -371,11 +603,22 @@ void RepositoryUtility::SerializeObject(std::ostream& fp, const String& name, co
 {
        fp << "object " << type << " \"" << name << "\" {\n";
 
-       if (object->Contains("templates"))
-               fp << "\t" << "import \"" << object->Get("templates") << "\"\n";
+       if (!object) {
+               fp << "}\n";
+               return;
+       }
+
+       if (object->Contains("import")) {
+               Array::Ptr imports = object->Get("import");
+
+               ObjectLock olock(imports);
+               BOOST_FOREACH(const String& import, imports) {
+                       fp << "\t" << "import \"" << import << "\"\n";
+               }
+       }
 
        BOOST_FOREACH(const Dictionary::Pair& kv, object) {
-               if (kv.first == "templates") {
+               if (kv.first == "import" || kv.first == "name") {
                        continue;
                } else {
                        fp << "\t" << kv.first << " = ";