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 "base/logger.hpp"
23 #include "base/application.hpp"
24 #include "base/convert.hpp"
25 #include "base/json.hpp"
26 #include "base/netstring.hpp"
27 #include "base/stdiostream.hpp"
28 #include "base/debug.hpp"
29 #include "base/objectlock.hpp"
30 #include "base/console.hpp"
31 #include <boost/foreach.hpp>
32 #include <boost/algorithm/string/join.hpp>
33 #include <boost/algorithm/string/replace.hpp>
34 #include <boost/algorithm/string/split.hpp>
35 #include <boost/algorithm/string/classification.hpp>
39 using namespace icinga;
41 Dictionary::Ptr RepositoryUtility::GetArgumentAttributes(const std::vector<std::string>& arguments)
43 Dictionary::Ptr attr = make_shared<Dictionary>();
45 BOOST_FOREACH(const String& kv, arguments) {
46 std::vector<String> tokens;
47 boost::algorithm::split(tokens, kv, boost::is_any_of("="));
49 if (tokens.size() == 2) {
50 attr->Set(tokens[0], tokens[1]);
52 Log(LogWarning, "cli")
53 << "Cannot parse passed attributes: " << boost::algorithm::join(tokens, "=");
59 String RepositoryUtility::GetRepositoryConfigPath(void)
61 return Application::GetSysconfDir() + "/icinga2/repository.d";
64 String RepositoryUtility::GetRepositoryObjectConfigPath(const String& type, const Dictionary::Ptr& object)
66 String path = GetRepositoryConfigPath() + "/";
71 else if (type == "Service")
72 path += "hosts/" + object->Get("host_name");
73 else if (type == "Zone")
75 else if (type == "Endpoints")
81 String RepositoryUtility::GetRepositoryObjectConfigFilePath(const String& type, const Dictionary::Ptr& object)
83 String path = GetRepositoryObjectConfigPath(type, object);
85 path += "/" + object->Get("name") + ".conf";
90 String RepositoryUtility::GetRepositoryChangeLogPath(void)
92 return Application::GetLocalStateDir() + "/lib/icinga2/repository/changes";
95 void RepositoryUtility::PrintObjects(std::ostream& fp, const String& type)
97 std::vector<String> objects = GetObjects(); //full path
99 BOOST_FOREACH(const String& object, objects) {
100 Dictionary::Ptr obj = GetObjectFromRepository(object);
103 fp << "Object Name: " << object << "\n";
104 fp << JsonEncode(obj);
109 /* public interface, only logs changes */
110 bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attr)
112 /* add a new changelog entry by timestamp */
113 String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
115 Dictionary::Ptr change = make_shared<Dictionary>();
117 change->Set("timestamp", Utility::GetTime());
118 change->Set("name", name);
119 change->Set("type", type);
120 change->Set("command", "add");
121 change->Set("attr", attr);
123 return WriteObjectToRepositoryChangeLog(path, change);
126 bool RepositoryUtility::RemoveObject(const String& name, const String& type, const Dictionary::Ptr& attr)
128 /* add a new changelog entry by timestamp */
129 String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
131 Dictionary::Ptr change = make_shared<Dictionary>();
133 change->Set("timestamp", Utility::GetTime());
134 change->Set("name", name);
135 change->Set("type", type);
136 change->Set("command", "remove");
137 change->Set("attr", attr); //required for service->host_name
139 return WriteObjectToRepositoryChangeLog(path, change);
142 bool RepositoryUtility::CommitChangeLog(void)
144 GetChangeLog(boost::bind(RepositoryUtility::CommitChange, _1, _2));
149 void RepositoryUtility::PrintChangeLog(std::ostream& fp)
151 Array::Ptr changelog = make_shared<Array>();
153 GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
155 ObjectLock olock(changelog);
157 std::cout << "Changes to be committed:\n";
159 BOOST_FOREACH(const Value& entry, changelog) {
160 std::cout << JsonEncode(entry) << "\n"; //TODO better formatting
164 bool RepositoryUtility::SetObjectAttribute(const String& name, const String& type, const String& attr, const Value& val)
166 //TODO: Implement modification commands
170 /* internal implementation when changes are committed */
171 bool RepositoryUtility::AddObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
173 String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
175 return WriteObjectToRepository(path, name, type, attr);
178 bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
180 String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
182 return RemoveObjectFileInternal(path);
185 bool RepositoryUtility::RemoveObjectFileInternal(const String& path)
187 if (!Utility::PathExists(path) ) {
188 Log(LogCritical, "cli", "Cannot remove '" + path + "'. Does not exist.");
192 if (unlink(path.CStr()) < 0) {
193 Log(LogCritical, "cli", "Cannot remove file '" + path +
194 "'. Failed with error code " + Convert::ToString(errno) + ", \"" + Utility::FormatErrorNumber(errno) + "\".");
201 bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& key, const Value& val, const Dictionary::Ptr& attr)
204 String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
206 Dictionary::Ptr obj = GetObjectFromRepository(path);
209 Log(LogCritical, "cli")
210 << "Can't get object " << name << " from repository.\n";
216 std::cout << "Writing object '" << name << "' to path '" << path << "'.\n";
218 //TODO: Create a patch file
219 if(!WriteObjectToRepository(path, name, type, obj)) {
220 Log(LogCritical, "cli")
221 << "Can't write object " << name << " to repository.\n";
228 bool RepositoryUtility::WriteObjectToRepository(const String& path, const String& name, const String& type, const Dictionary::Ptr& item)
230 Log(LogInformation, "cli", "Dumping config items to file '" + path + "'");
232 Utility::MkDirP(Utility::DirName(path), 0755);
234 String tempPath = path + ".tmp";
236 std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
237 SerializeObject(fp, name, type, item);
242 _unlink(path.CStr());
245 if (rename(tempPath.CStr(), path.CStr()) < 0) {
246 BOOST_THROW_EXCEPTION(posix_error()
247 << boost::errinfo_api_function("rename")
248 << boost::errinfo_errno(errno)
249 << boost::errinfo_file_name(tempPath));
255 Dictionary::Ptr RepositoryUtility::GetObjectFromRepository(const String& filename)
257 //TODO: Parse existing configuration objects
258 return Dictionary::Ptr();
261 bool RepositoryUtility::WriteObjectToRepositoryChangeLog(const String& path, const Dictionary::Ptr& item)
263 Log(LogInformation, "cli", "Dumping changelog items to file '" + path + "'");
265 Utility::MkDirP(Utility::DirName(path), 0750);
267 String tempPath = path + ".tmp";
269 std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
270 fp << JsonEncode(item);
274 _unlink(path.CStr());
277 if (rename(tempPath.CStr(), path.CStr()) < 0) {
278 BOOST_THROW_EXCEPTION(posix_error()
279 << boost::errinfo_api_function("rename")
280 << boost::errinfo_errno(errno)
281 << boost::errinfo_file_name(tempPath));
287 Dictionary::Ptr RepositoryUtility::GetObjectFromRepositoryChangeLog(const String& filename)
290 fp.open(filename.CStr(), std::ifstream::in);
293 return Dictionary::Ptr();
295 String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
299 return JsonDecode(content);
305 std::vector<String> RepositoryUtility::GetObjects(void)
307 std::vector<String> objects;
308 String path = GetRepositoryConfigPath() + "/";
310 Utility::Glob(path + "/*.conf",
311 boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile);
316 void RepositoryUtility::CollectObjects(const String& object_file, std::vector<String>& objects)
319 << "Adding object: '" << object_file << "'.";
321 objects.push_back(object_file);
325 bool RepositoryUtility::GetChangeLog(const boost::function<void (const Dictionary::Ptr&, const String&)>& callback)
327 std::vector<String> changelog;
328 String path = GetRepositoryChangeLogPath() + "/";
330 Utility::Glob(path + "/*.change",
331 boost::bind(&RepositoryUtility::CollectChangeLog, _1, boost::ref(changelog)), GlobFile);
333 /* sort by timestamp ascending */
334 std::sort(changelog.begin(), changelog.end());
336 BOOST_FOREACH(const String& entry, changelog) {
337 String file = path + entry + ".change";
338 Dictionary::Ptr change = GetObjectFromRepositoryChangeLog(file);
340 Log(LogInformation, "cli")
341 << "Collecting entry " << entry << "\n";
344 callback(change, file);
350 void RepositoryUtility::CollectChangeLog(const String& change_file, std::vector<String>& changelog)
352 String file = Utility::BaseName(change_file);
353 boost::algorithm::replace_all(file, ".change", "");
355 Log(LogDebug, "cli", "Adding change file: " + file);
356 changelog.push_back(file);
359 void RepositoryUtility::CommitChange(const Dictionary::Ptr& change, const String& path)
361 Log(LogInformation, "cli")
362 << "Got change " << change->Get("name");
364 String name = change->Get("name");
365 String type = change->Get("type");
366 String command = change->Get("command");
367 Dictionary::Ptr attr;
369 if (change->Contains("attr")) {
370 attr = change->Get("attr");
373 bool success = false;
375 if (command == "add") {
376 success = AddObjectInternal(name, type, attr);
378 else if (command == "remove") {
379 success = RemoveObjectInternal(name, type, attr);
383 Log(LogInformation, "cli")
384 << "Removing changelog file '" << path << "'.";
385 RemoveObjectFileInternal(path);
389 void RepositoryUtility::CollectChange(const Dictionary::Ptr& change, Array::Ptr& changes)
391 changes->Add(change);
396 * print helpers for configuration
397 * TODO: Move into a separate class
399 void RepositoryUtility::SerializeObject(std::ostream& fp, const String& name, const String& type, const Dictionary::Ptr& object)
401 fp << "object " << type << " \"" << name << "\" {\n";
408 if (object->Contains("import")) {
409 Array::Ptr imports = object->Get("import");
411 ObjectLock olock(imports);
412 BOOST_FOREACH(const String& import, imports) {
413 fp << "\t" << "import \"" << import << "\"\n";
417 BOOST_FOREACH(const Dictionary::Pair& kv, object) {
418 if (kv.first == "import" || kv.first == "name") {
421 fp << "\t" << kv.first << " = ";
422 FormatValue(fp, kv.second);
429 void RepositoryUtility::FormatValue(std::ostream& fp, const Value& val)
431 if (val.IsObjectType<Array>()) {
432 FormatArray(fp, val);
436 if (val.IsString()) {
437 fp << "\"" << Convert::ToString(val) << "\"";
441 fp << Convert::ToString(val);
444 void RepositoryUtility::FormatArray(std::ostream& fp, const Array::Ptr& arr)
451 ObjectLock olock(arr);
452 BOOST_FOREACH(const Value& value, arr) {
458 FormatValue(fp, value);