1 /******************************************************************************
3 * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "cli/repositoryutility.hpp"
21 #include "cli/clicommand.hpp"
22 #include "config/configtype.hpp"
23 #include "config/configcompilercontext.hpp"
24 #include "config/configcompiler.hpp"
25 #include "base/logger.hpp"
26 #include "base/application.hpp"
27 #include "base/convert.hpp"
28 #include "base/json.hpp"
29 #include "base/netstring.hpp"
30 #include "base/tlsutility.hpp"
31 #include "base/stdiostream.hpp"
32 #include "base/debug.hpp"
33 #include "base/objectlock.hpp"
34 #include "base/console.hpp"
35 #include <boost/foreach.hpp>
36 #include <boost/algorithm/string/join.hpp>
37 #include <boost/algorithm/string/replace.hpp>
38 #include <boost/algorithm/string/split.hpp>
39 #include <boost/algorithm/string/classification.hpp>
40 #include <boost/algorithm/string/case_conv.hpp>
41 #include <boost/regex.hpp>
45 using namespace icinga;
47 Dictionary::Ptr RepositoryUtility::GetArgumentAttributes(const std::vector<std::string>& arguments)
49 Dictionary::Ptr attrs = make_shared<Dictionary>();
51 BOOST_FOREACH(const String& kv, arguments) {
52 std::vector<String> tokens;
53 boost::algorithm::split(tokens, kv, boost::is_any_of("="));
55 if (tokens.size() != 2) {
56 Log(LogWarning, "cli")
57 << "Cannot parse passed attributes: " << boost::algorithm::join(tokens, "=");
64 value = Convert::ToDouble(tokens[1]);
69 attrs->Set(tokens[0], value);
75 String RepositoryUtility::GetRepositoryConfigPath(void)
77 return Application::GetSysconfDir() + "/icinga2/repository.d";
80 String RepositoryUtility::GetRepositoryObjectConfigPath(const String& type, const Dictionary::Ptr& object)
82 String path = GetRepositoryConfigPath() + "/";
86 else if (type == "Service")
87 path += "hosts/" + object->Get("host_name");
88 else if (type == "Zone")
90 else if (type == "Endpoint")
96 bool RepositoryUtility::FilterRepositoryObjects(const String& type, const String& path)
99 boost::regex expr("hosts/[^/]*.conf", boost::regex::icase);
101 return boost::regex_search(path.GetData(), what, expr);
103 else if (type == "Service")
104 return Utility::Match("*hosts/*/*.conf", path);
105 else if (type == "Zone")
106 return Utility::Match("*zones/*.conf", path);
107 else if (type == "Endpoints")
108 return Utility::Match("*endpoints/*.conf", path);
113 String RepositoryUtility::GetRepositoryObjectConfigFilePath(const String& type, const Dictionary::Ptr& object)
115 String path = GetRepositoryObjectConfigPath(type, object);
117 path += "/" + object->Get("name") + ".conf";
122 String RepositoryUtility::GetRepositoryChangeLogPath(void)
124 return Application::GetLocalStateDir() + "/lib/icinga2/repository/changes";
128 void RepositoryUtility::PrintObjects(std::ostream& fp, const String& type)
130 std::vector<String> objects = GetObjects(); //full path
132 BOOST_FOREACH(const String& object, objects) {
133 if (!FilterRepositoryObjects(type, object)) {
135 << "Ignoring object '" << object << "'. Type '" << type << "' does not match.";
139 String file = Utility::BaseName(object);
140 boost::algorithm::replace_all(file, ".conf", "");
142 fp << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << type << ConsoleColorTag(Console_Normal)
143 << " '" << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << file << ConsoleColorTag(Console_Normal) << "'";
145 String prefix = Utility::DirName(object);
147 if (type == "Service") {
148 std::vector<String> tokens;
149 boost::algorithm::split(tokens, prefix, boost::is_any_of("/"));
151 String host_name = tokens[tokens.size()-1];
152 fp << " (on " << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << "Host" << ConsoleColorTag(Console_Normal)
153 << " '" << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << host_name << ConsoleColorTag(Console_Normal) << "')";
160 Dictionary::Ptr obj = GetObjectFromRepository(object); //TODO: config parser not implemented yet!
163 fp << JsonEncode(obj);
168 void RepositoryUtility::PrintChangeLog(std::ostream& fp)
170 Array::Ptr changelog = make_shared<Array>();
172 GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
174 ObjectLock olock(changelog);
176 std::cout << "Changes to be committed:\n\n";
178 BOOST_FOREACH(const Value& entry, changelog) {
179 FormatChangelogEntry(std::cout, entry);
183 class RepositoryTypeRuleUtilities : public TypeRuleUtilities
186 virtual bool ValidateName(const String& type, const String& name, String *hint) const
192 /* modify objects and write changelog */
193 bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attrs)
195 /* add a new changelog entry by timestamp */
196 String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(Utility::GetTime()) + "-" + type + "-" + SHA256(name) + ".change";
198 Dictionary::Ptr change = make_shared<Dictionary>();
200 change->Set("timestamp", Utility::GetTime());
201 change->Set("name", name);
202 change->Set("type", type);
203 change->Set("command", "add");
204 change->Set("attrs", attrs);
206 ConfigCompilerContext::GetInstance()->Reset();
208 String fname, fragment;
209 BOOST_FOREACH(boost::tie(fname, fragment), ConfigFragmentRegistry::GetInstance()->GetItems()) {
210 ConfigCompiler::CompileText(fname, fragment);
213 ConfigType::Ptr ctype = ConfigType::GetByName(type);
216 Log(LogCritical, "cli")
217 << "No validation type available for '" << type << "'.";
219 Dictionary::Ptr vattrs = attrs->ShallowClone();
220 vattrs->Set("__name", vattrs->Get("name"));
221 vattrs->Remove("name");
222 vattrs->Remove("import");
223 vattrs->Set("type", type);
225 RepositoryTypeRuleUtilities utils;
226 ctype->ValidateItem(name, vattrs, DebugInfo(), &utils);
228 int warnings = 0, errors = 0;
230 BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
231 String logmsg = String("Config ") + (message.Error ? "error" : "warning") + ": " + message.Text;
234 Log(LogCritical, "config", logmsg);
237 Log(LogWarning, "config", logmsg);
246 return WriteObjectToRepositoryChangeLog(path, change);
249 bool RepositoryUtility::RemoveObject(const String& name, const String& type, const Dictionary::Ptr& attrs)
251 /* add a new changelog entry by timestamp */
252 String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(Utility::GetTime()) + "-" + type + "-" + SHA256(name) + ".change";
254 Dictionary::Ptr change = make_shared<Dictionary>();
256 change->Set("timestamp", Utility::GetTime());
257 change->Set("name", name);
258 change->Set("type", type);
259 change->Set("command", "remove");
260 change->Set("attrs", attrs); //required for service->host_name
262 return WriteObjectToRepositoryChangeLog(path, change);
265 bool RepositoryUtility::SetObjectAttribute(const String& name, const String& type, const String& attr, const Value& val)
267 //TODO: Implement modification commands
271 bool RepositoryUtility::ClearChangeLog(void)
273 GetChangeLog(boost::bind(RepositoryUtility::ClearChange, _1, _2));
278 bool RepositoryUtility::ChangeLogHasPendingChanges(void)
280 Array::Ptr changelog = make_shared<Array>();
281 GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
283 return changelog->GetLength() > 0;
286 /* commit changelog */
287 bool RepositoryUtility::CommitChangeLog(void)
289 GetChangeLog(boost::bind(RepositoryUtility::CommitChange, _1, _2));
294 /* write/read from changelog repository */
295 bool RepositoryUtility::WriteObjectToRepositoryChangeLog(const String& path, const Dictionary::Ptr& item)
297 Log(LogInformation, "cli", "Dumping changelog items to file '" + path + "'");
299 Utility::MkDirP(Utility::DirName(path), 0750);
301 String tempPath = path + ".tmp";
303 std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
304 fp << JsonEncode(item);
308 _unlink(path.CStr());
311 if (rename(tempPath.CStr(), path.CStr()) < 0) {
312 BOOST_THROW_EXCEPTION(posix_error()
313 << boost::errinfo_api_function("rename")
314 << boost::errinfo_errno(errno)
315 << boost::errinfo_file_name(tempPath));
321 Dictionary::Ptr RepositoryUtility::GetObjectFromRepositoryChangeLog(const String& filename)
324 fp.open(filename.CStr(), std::ifstream::in);
327 return Dictionary::Ptr();
329 String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
333 return JsonDecode(content);
336 /* internal implementation when changes are committed */
337 bool RepositoryUtility::AddObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attrs)
339 String path = GetRepositoryObjectConfigPath(type, attrs) + "/" + name + ".conf";
341 return WriteObjectToRepository(path, name, type, attrs);
344 bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attrs)
346 String path = GetRepositoryObjectConfigPath(type, attrs) + "/" + name + ".conf";
347 bool success = RemoveObjectFileInternal(path);
349 /* special treatment for hosts -> remove the services too */
350 if (type == "Host") {
351 path = GetRepositoryObjectConfigPath(type, attrs) + "/" + name;
353 std::vector<String> files;
354 Utility::GlobRecursive(path, "*.conf",
355 boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(files)), GlobFile);
357 BOOST_FOREACH(const String& file, files) {
358 RemoveObjectFileInternal(file);
371 bool RepositoryUtility::RemoveObjectFileInternal(const String& path)
373 if (!Utility::PathExists(path) ) {
374 Log(LogCritical, "cli", "Cannot remove '" + path + "'. Does not exist.");
378 if (unlink(path.CStr()) < 0) {
379 Log(LogCritical, "cli", "Cannot remove path '" + path +
380 "'. Failed with error code " + Convert::ToString(errno) + ", \"" + Utility::FormatErrorNumber(errno) + "\".");
387 bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& key, const Value& val, const Dictionary::Ptr& attrs)
390 String path = GetRepositoryObjectConfigPath(type, attrs) + "/" + name + ".conf";
392 Dictionary::Ptr obj = GetObjectFromRepository(path); //TODO
395 Log(LogCritical, "cli")
396 << "Can't get object " << name << " from repository.\n";
402 std::cout << "Writing object '" << name << "' to path '" << path << "'.\n";
404 //TODO: Create a patch file
405 if(!WriteObjectToRepository(path, name, type, obj)) {
406 Log(LogCritical, "cli")
407 << "Can't write object " << name << " to repository.\n";
414 bool RepositoryUtility::WriteObjectToRepository(const String& path, const String& name, const String& type, const Dictionary::Ptr& item)
416 Log(LogInformation, "cli")
417 << "Dumping config object '" << name << "' to file '" << path << "'";
419 Utility::MkDirP(Utility::DirName(path), 0755);
421 String tempPath = path + ".tmp";
423 std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
424 SerializeObject(fp, name, type, item);
429 _unlink(path.CStr());
432 if (rename(tempPath.CStr(), path.CStr()) < 0) {
433 BOOST_THROW_EXCEPTION(posix_error()
434 << boost::errinfo_api_function("rename")
435 << boost::errinfo_errno(errno)
436 << boost::errinfo_file_name(tempPath));
442 Dictionary::Ptr RepositoryUtility::GetObjectFromRepository(const String& filename)
444 //TODO: Parse existing configuration objects
445 return Dictionary::Ptr();
452 std::vector<String> RepositoryUtility::GetObjects(void)
454 std::vector<String> objects;
455 String path = GetRepositoryConfigPath();
457 Utility::GlobRecursive(path, "*.conf",
458 boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile);
463 void RepositoryUtility::CollectObjects(const String& object_file, std::vector<String>& objects)
466 << "Adding object: '" << object_file << "'.";
468 objects.push_back(object_file);
472 bool RepositoryUtility::GetChangeLog(const boost::function<void (const Dictionary::Ptr&, const String&)>& callback)
474 std::vector<String> changelog;
475 String path = GetRepositoryChangeLogPath() + "/";
477 Utility::Glob(path + "/*.change",
478 boost::bind(&RepositoryUtility::CollectChangeLog, _1, boost::ref(changelog)), GlobFile);
480 /* sort by timestamp ascending */
481 std::sort(changelog.begin(), changelog.end());
483 BOOST_FOREACH(const String& entry, changelog) {
484 String file = path + entry + ".change";
485 Dictionary::Ptr change = GetObjectFromRepositoryChangeLog(file);
488 << "Collecting entry " << entry << "\n";
491 callback(change, file);
497 void RepositoryUtility::CollectChangeLog(const String& change_file, std::vector<String>& changelog)
499 String file = Utility::BaseName(change_file);
500 boost::algorithm::replace_all(file, ".change", "");
503 << "Adding change file: '" << file << "'.";
505 changelog.push_back(file);
508 void RepositoryUtility::CollectChange(const Dictionary::Ptr& change, Array::Ptr& changes)
510 changes->Add(change);
514 * Commit Changelog entry
516 void RepositoryUtility::CommitChange(const Dictionary::Ptr& change, const String& path)
519 << "Got change " << change->Get("name");
521 String name = change->Get("name");
522 String type = change->Get("type");
523 String command = change->Get("command");
524 Dictionary::Ptr attrs;
526 if (change->Contains("attrs")) {
527 attrs = change->Get("attrs");
530 bool success = false;
532 if (command == "add") {
533 success = AddObjectInternal(name, type, attrs);
535 else if (command == "remove") {
536 success = RemoveObjectInternal(name, type, attrs);
540 Log(LogNotice, "cli")
541 << "Removing changelog file '" << path << "'.";
542 RemoveObjectFileInternal(path);
547 * Clear Changelog entry
549 void RepositoryUtility::ClearChange(const Dictionary::Ptr& change, const String& path)
552 << "Clearing change " << change->Get("name");
554 Log(LogInformation, "cli")
555 << "Removing changelog file '" << path << "'.";
557 RemoveObjectFileInternal(path);
561 * Print Changelog helpers
563 void RepositoryUtility::FormatChangelogEntry(std::ostream& fp, const Dictionary::Ptr& change)
568 if (change->Get("command") == "add")
570 if (change->Get("command") == "remove")
573 String type = change->Get("type");
574 boost::algorithm::to_lower(type);
575 Dictionary::Ptr attrs = change->Get("attrs");
577 fp << " " << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << type << ConsoleColorTag(Console_Normal) << " '";
578 fp << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << change->Get("name") << ConsoleColorTag(Console_Normal) << "'";
580 if (!attrs || attrs->GetLength() == 0) {
585 fp << " with attributes: \n";
587 BOOST_FOREACH(const Dictionary::Pair& kv, attrs) {
589 if (kv.first == "name")
592 fp << std::setw(4) << " " << ConsoleColorTag(Console_ForegroundGreen) << kv.first << ConsoleColorTag(Console_Normal) << " = ";
593 FormatValue(fp, kv.second);
599 * print helpers for configuration
600 * TODO: Move into a separate class
602 void RepositoryUtility::SerializeObject(std::ostream& fp, const String& name, const String& type, const Dictionary::Ptr& object)
604 fp << "object " << type << " \"" << name << "\" {\n";
611 if (object->Contains("import")) {
612 Array::Ptr imports = object->Get("import");
614 ObjectLock olock(imports);
615 BOOST_FOREACH(const String& import, imports) {
616 fp << "\t" << "import \"" << import << "\"\n";
620 BOOST_FOREACH(const Dictionary::Pair& kv, object) {
621 if (kv.first == "import" || kv.first == "name") {
624 fp << "\t" << kv.first << " = ";
625 FormatValue(fp, kv.second);
632 void RepositoryUtility::FormatValue(std::ostream& fp, const Value& val)
634 if (val.IsObjectType<Array>()) {
635 FormatArray(fp, val);
639 if (val.IsString()) {
640 fp << "\"" << Convert::ToString(val) << "\"";
644 fp << Convert::ToString(val);
647 void RepositoryUtility::FormatArray(std::ostream& fp, const Array::Ptr& arr)
654 ObjectLock olock(arr);
655 BOOST_FOREACH(const Value& value, arr) {
661 FormatValue(fp, value);