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/serializer.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>
36 using namespace icinga;
38 String RepositoryUtility::GetRepositoryDPath(void)
40 return Application::GetSysconfDir() + "/icinga2/repository.d";
43 String RepositoryUtility::GetRepositoryDObjectsPath(const String& type, const String& hostname)
46 return GetRepositoryDPath() + "/hosts";
47 else if (type == "Service")
48 return GetRepositoryDPath() + "/hosts/" + hostname;
49 else if (type == "Zone")
50 return GetRepositoryDPath() + "/zones";
51 else if (type == "Endpoints")
52 return GetRepositoryDPath() + "/endpoints";
54 return GetRepositoryDPath();
57 String RepositoryUtility::GetRepositoryChangeLogPath(void)
59 return Application::GetLocalStateDir() + "/lib/icinga2/repository";
62 void RepositoryUtility::PrintObjects(std::ostream& fp, const String& type)
64 std::vector<String> objects;
65 GetObjects(type, objects);
67 BOOST_FOREACH(const String& object, objects) {
68 Dictionary::Ptr obj = GetObjectFromRepository(GetRepositoryDObjectsPath(type) + "/" + object + ".conf");
71 fp << "Object Name: " << object << "\n";
72 fp << JsonSerialize(obj);
77 /* public interface, only logs changes */
78 bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attr)
80 /* add a new changelog entry by timestamp */
81 String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
83 Dictionary::Ptr change = make_shared<Dictionary>();
85 change->Set("timestamp", Utility::GetTime());
86 change->Set("name", name);
87 change->Set("type", type);
88 change->Set("command", "add");
89 change->Set("attr", attr);
91 return WriteObjectToRepositoryChangeLog(path, change);
94 bool RepositoryUtility::RemoveObject(const String& name, const String& type)
96 /* add a new changelog entry by timestamp */
97 String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
99 Dictionary::Ptr change = make_shared<Dictionary>();
101 change->Set("timestamp", Utility::GetTime());
102 change->Set("name", name);
103 change->Set("type", type);
104 change->Set("command", "remove");
106 return WriteObjectToRepositoryChangeLog(path, change);
109 bool RepositoryUtility::CommitChangeLog(void)
111 GetChangeLog(boost::bind(RepositoryUtility::CommitChange, _1));
116 void RepositoryUtility::PrintChangeLog(std::ostream& fp)
118 Array::Ptr changelog = make_shared<Array>();
120 GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
122 ObjectLock olock(changelog);
124 std::cout << "Changes to be committed:\n";
126 BOOST_FOREACH(const Value& entry, changelog) {
127 std::cout << JsonSerialize(entry) << "\n"; //TODO better formatting
131 bool RepositoryUtility::SetObjectAttribute(const String& name, const String& type, const String& attr, const Value& val)
133 //TODO: Implement modification commands
137 /* internal implementation when changes are committed */
138 bool RepositoryUtility::AddObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
140 String path = GetRepositoryDObjectsPath(type, name) + "/" + name + ".conf";
142 return WriteObjectToRepository(path, name, type, attr);
145 bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type)
147 String path = GetRepositoryDObjectsPath(type, name) + "/" + name + ".conf";
149 return RemoveObjectFileInternal(path);
152 bool RepositoryUtility::RemoveObjectFileInternal(const String& path)
154 if (!Utility::PathExists(path) ) {
155 Log(LogCritical, "cli", "Cannot remove '" + path + "'. Does not exist.");
159 if (unlink(path.CStr()) < 0) {
160 Log(LogCritical, "cli", "Cannot remove file '" + path +
161 "'. Failed with error code " + Convert::ToString(errno) + ", \"" + Utility::FormatErrorNumber(errno) + "\".");
168 bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& attr, const Value& val)
171 String path = GetRepositoryDObjectsPath(type, name) + "/" + name + ".conf";
173 Dictionary::Ptr obj = GetObjectFromRepository(path);
176 Log(LogCritical, "cli")
177 << "Can't get object " << name << " from repository.\n";
183 std::cout << "Writing object '" << name << "' to path '" << path << "'.\n";
185 //TODO: Create a patch file
186 if(!WriteObjectToRepository(path, name, type, obj)) {
187 Log(LogCritical, "cli")
188 << "Can't write object " << name << " to repository.\n";
195 bool RepositoryUtility::WriteObjectToRepository(const String& path, const String& name, const String& type, const Dictionary::Ptr& item)
197 Log(LogInformation, "cli", "Dumping config items to file '" + path + "'");
199 Utility::MkDirP(Utility::DirName(path), 0755);
201 String tempPath = path + ".tmp";
203 std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
204 SerializeObject(fp, name, type, item);
209 _unlink(filename.CStr());
212 if (rename(tempPath.CStr(), path.CStr()) < 0) {
213 BOOST_THROW_EXCEPTION(posix_error()
214 << boost::errinfo_api_function("rename")
215 << boost::errinfo_errno(errno)
216 << boost::errinfo_file_name(tempPath));
222 Dictionary::Ptr RepositoryUtility::GetObjectFromRepository(const String& filename)
224 //TODO: Parse existing configuration objects
225 return Dictionary::Ptr();
228 bool RepositoryUtility::WriteObjectToRepositoryChangeLog(const String& path, const Dictionary::Ptr& item)
230 Log(LogInformation, "cli", "Dumping changelog 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 fp << JsonSerialize(item);
241 _unlink(filename.CStr());
244 if (rename(tempPath.CStr(), path.CStr()) < 0) {
245 BOOST_THROW_EXCEPTION(posix_error()
246 << boost::errinfo_api_function("rename")
247 << boost::errinfo_errno(errno)
248 << boost::errinfo_file_name(tempPath));
254 Dictionary::Ptr RepositoryUtility::GetObjectFromRepositoryChangeLog(const String& filename)
257 fp.open(filename.CStr(), std::ifstream::in);
260 return Dictionary::Ptr();
262 String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
266 return JsonDeserialize(content);
273 bool RepositoryUtility::GetObjects(const String& type, std::vector<String>& objects)
275 String path = GetRepositoryDPath() + "/";
277 if (type == "service")
282 if (!Utility::Glob(path + "/*.conf",
283 boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile)) {
284 Log(LogCritical, "cli", "Cannot access path '" + path + "'.");
291 void RepositoryUtility::CollectObjects(const String& object_file, std::vector<String>& objects)
293 String object = Utility::BaseName(object_file);
294 boost::algorithm::replace_all(object, ".conf", "");
296 Log(LogDebug, "cli", "Adding object: " + object);
297 objects.push_back(object);
301 bool RepositoryUtility::GetChangeLog(const boost::function<void (const Dictionary::Ptr&)>& callback)
303 std::vector<String> changelog;
304 String path = GetRepositoryChangeLogPath() + "/";
306 if (!Utility::Glob(path + "/*.change",
307 boost::bind(&RepositoryUtility::CollectChangeLog, _1, boost::ref(changelog)), GlobFile)) {
308 Log(LogCritical, "cli", "Cannot access path '" + path + "'.");
312 /* sort by timestamp ascending */
313 std::sort(changelog.begin(), changelog.end());
315 BOOST_FOREACH(const String& entry, changelog) {
316 Dictionary::Ptr change = GetObjectFromRepositoryChangeLog(path + entry + ".change");
318 Log(LogInformation, "cli")
319 << "Collecting entry " << entry << "\n";
328 void RepositoryUtility::CollectChangeLog(const String& change_file, std::vector<String>& changelog)
330 String file = Utility::BaseName(change_file);
331 boost::algorithm::replace_all(file, ".change", "");
333 Log(LogDebug, "cli", "Adding change file: " + file);
334 changelog.push_back(file);
337 void RepositoryUtility::CommitChange(const Dictionary::Ptr& change)
339 Log(LogInformation, "cli")
340 << "Got change " << change->Get("name");
342 String name = change->Get("name");
343 String type = change->Get("type");
344 String command = change->Get("command");
345 Dictionary::Ptr attr;
347 if (change->Contains("attr")) {
348 attr = change->Get("attr");
351 if (command == "add") {
352 AddObjectInternal(name, type, attr);
354 else if (command == "remove") {
355 RemoveObjectInternal(name, type);
359 void RepositoryUtility::CollectChange(const Dictionary::Ptr& change, Array::Ptr& changes)
361 changes->Add(change);
366 * print helpers for configuration
367 * TODO: Move into a separate class
369 void RepositoryUtility::SerializeObject(std::ostream& fp, const String& name, const String& type, const Dictionary::Ptr& object)
371 fp << "object " << type << " \"" << name << "\" {\n";
372 BOOST_FOREACH(const Dictionary::Pair& kv, object) {
373 fp << "\t" << kv.first << " = ";
374 FormatValue(fp, kv.second);
380 void RepositoryUtility::FormatValue(std::ostream& fp, const Value& val)
382 if (val.IsObjectType<Array>()) {
383 FormatArray(fp, val);
387 if (val.IsString()) {
388 fp << "\"" << Convert::ToString(val) << "\"";
392 fp << Convert::ToString(val);
395 void RepositoryUtility::FormatArray(std::ostream& fp, const Array::Ptr& arr)
402 ObjectLock olock(arr);
403 BOOST_FOREACH(const Value& value, arr) {
409 FormatValue(fp, value);