]> granicus.if.org Git - icinga2/blob - lib/cli/repositoryutility.cpp
Cli: Fix integer formatting in repository command
[icinga2] / lib / cli / repositoryutility.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
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.                     *
9  *                                                                            *
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.                               *
14  *                                                                            *
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  ******************************************************************************/
19
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>
36 #include <boost/algorithm/string/case_conv.hpp>
37 #include <boost/regex.hpp>
38 #include <fstream>
39 #include <iostream>
40
41 using namespace icinga;
42
43 Dictionary::Ptr RepositoryUtility::GetArgumentAttributes(const std::vector<std::string>& arguments)
44 {
45         Dictionary::Ptr attrs = make_shared<Dictionary>();
46
47         BOOST_FOREACH(const String& kv, arguments) {
48                 std::vector<String> tokens;
49                 boost::algorithm::split(tokens, kv, boost::is_any_of("="));
50
51                 if (tokens.size() != 2) {
52                         Log(LogWarning, "cli")
53                             << "Cannot parse passed attributes: " << boost::algorithm::join(tokens, "=");
54                         continue;
55                 }
56
57                 Value value;
58
59                 try {
60                         value = Convert::ToDouble(tokens[1]);
61                 } catch (...) {
62                         value = tokens[1];
63                 }
64
65                 attrs->Set(tokens[0], value);
66         }
67
68         return attrs;
69 }
70
71 String RepositoryUtility::GetRepositoryConfigPath(void)
72 {
73         return Application::GetSysconfDir() + "/icinga2/repository.d";
74 }
75
76 String RepositoryUtility::GetRepositoryObjectConfigPath(const String& type, const Dictionary::Ptr& object)
77 {
78         String path = GetRepositoryConfigPath() + "/";
79
80         if (type == "Host")
81                 path += "hosts";
82         else if (type == "Service")
83                 path += "hosts/" + object->Get("host_name");
84         else if (type == "Zone")
85                 path += "zones";
86         else if (type == "Endpoints")
87                 path += "endpoints";
88
89         return path;
90 }
91
92 bool RepositoryUtility::FilterRepositoryObjects(const String& type, const String& path)
93 {
94         if (type == "Host") {
95                 boost::regex expr("hosts/[^/]*.conf", boost::regex::icase);
96                 boost::smatch what;
97                 return boost::regex_search(path.GetData(), what, expr);
98         }
99         else if (type == "Service")
100                 return Utility::Match("*hosts/*/*.conf", path);
101         else if (type == "Zone")
102                 return Utility::Match("*zones/*.conf", path);
103         else if (type == "Endpoints")
104                 return Utility::Match("*endpoints/*.conf", path);
105
106         return false;
107 }
108
109 String RepositoryUtility::GetRepositoryObjectConfigFilePath(const String& type, const Dictionary::Ptr& object)
110 {
111         String path = GetRepositoryObjectConfigPath(type, object);
112
113         path += "/" + object->Get("name") + ".conf";
114
115         return path;
116 }
117
118 String RepositoryUtility::GetRepositoryChangeLogPath(void)
119 {
120         return Application::GetLocalStateDir() + "/lib/icinga2/repository/changes";
121 }
122
123 /* printers */
124 void RepositoryUtility::PrintObjects(std::ostream& fp, const String& type)
125 {
126         std::vector<String> objects = GetObjects(); //full path
127
128         BOOST_FOREACH(const String& object, objects) {
129                 if (!FilterRepositoryObjects(type, object)) {
130                         Log(LogDebug, "cli")
131                             << "Ignoring object '" << object << "'. Type '" << type << "' does not match.";
132                         continue;
133                 }
134
135                 fp << "Object Path: " << object << "\n";
136
137                 Dictionary::Ptr obj = GetObjectFromRepository(object); //TODO: config parser not implemented yet!
138
139                 if (obj)
140                         fp << JsonEncode(obj);
141         }
142 }
143
144 void RepositoryUtility::PrintChangeLog(std::ostream& fp)
145 {
146         Array::Ptr changelog = make_shared<Array>();
147
148         GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
149
150         ObjectLock olock(changelog);
151
152         std::cout << "Changes to be committed:\n\n";
153
154         BOOST_FOREACH(const Value& entry, changelog) {
155                 FormatChangelogEntry(std::cout, entry);
156         }
157 }
158
159 /* modify objects and write changelog */
160 bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attr)
161 {
162         /* add a new changelog entry by timestamp */
163         String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
164
165         Dictionary::Ptr change = make_shared<Dictionary>();
166
167         change->Set("timestamp", Utility::GetTime());
168         change->Set("name", name);
169         change->Set("type", type);
170         change->Set("command", "add");
171         change->Set("attr", attr);
172
173         return WriteObjectToRepositoryChangeLog(path, change);
174 }
175
176 bool RepositoryUtility::RemoveObject(const String& name, const String& type, const Dictionary::Ptr& attr)
177 {
178         /* add a new changelog entry by timestamp */
179         String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
180
181         Dictionary::Ptr change = make_shared<Dictionary>();
182
183         change->Set("timestamp", Utility::GetTime());
184         change->Set("name", name);
185         change->Set("type", type);
186         change->Set("command", "remove");
187         change->Set("attr", attr); //required for service->host_name
188
189         return WriteObjectToRepositoryChangeLog(path, change);
190 }
191
192 bool RepositoryUtility::SetObjectAttribute(const String& name, const String& type, const String& attr, const Value& val)
193 {
194         //TODO: Implement modification commands
195         return true;
196 }
197
198 /* commit changelog */
199 bool RepositoryUtility::CommitChangeLog(void)
200 {
201         GetChangeLog(boost::bind(RepositoryUtility::CommitChange, _1, _2));
202
203         return true;
204 }
205
206 /* write/read from changelog repository */
207 bool RepositoryUtility::WriteObjectToRepositoryChangeLog(const String& path, const Dictionary::Ptr& item)
208 {
209         Log(LogInformation, "cli", "Dumping changelog items to file '" + path + "'");
210
211         Utility::MkDirP(Utility::DirName(path), 0750);
212
213         String tempPath = path + ".tmp";
214
215         std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
216         fp << JsonEncode(item);
217         fp.close();
218
219 #ifdef _WIN32
220         _unlink(path.CStr());
221 #endif /* _WIN32 */
222
223         if (rename(tempPath.CStr(), path.CStr()) < 0) {
224                 BOOST_THROW_EXCEPTION(posix_error()
225                     << boost::errinfo_api_function("rename")
226                     << boost::errinfo_errno(errno)
227                     << boost::errinfo_file_name(tempPath));
228         }
229
230         return true;
231 }
232
233 Dictionary::Ptr RepositoryUtility::GetObjectFromRepositoryChangeLog(const String& filename)
234 {
235         std::fstream fp;
236         fp.open(filename.CStr(), std::ifstream::in);
237
238         if (!fp)
239                 return Dictionary::Ptr();
240
241         String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
242
243         fp.close();
244
245         return JsonDecode(content);
246 }
247
248 /* internal implementation when changes are committed */
249 bool RepositoryUtility::AddObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
250 {
251         String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
252
253         return WriteObjectToRepository(path, name, type, attr);
254 }
255
256 bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
257 {
258         String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
259
260         return RemoveObjectFileInternal(path);
261 }
262
263 bool RepositoryUtility::RemoveObjectFileInternal(const String& path)
264 {
265         if (!Utility::PathExists(path) ) {
266                 Log(LogCritical, "cli", "Cannot remove '" + path + "'. Does not exist.");
267                 return false;
268         }
269
270         if (unlink(path.CStr()) < 0) {
271                 Log(LogCritical, "cli", "Cannot remove file '" + path +
272                     "'. Failed with error code " + Convert::ToString(errno) + ", \"" + Utility::FormatErrorNumber(errno) + "\".");
273                 return false;
274         }
275
276         return true;
277 }
278
279 bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& key, const Value& val, const Dictionary::Ptr& attr)
280 {
281         //Fixme
282         String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
283
284         Dictionary::Ptr obj = GetObjectFromRepository(path); //TODO
285
286         if (!obj) {
287                 Log(LogCritical, "cli")
288                     << "Can't get object " << name << " from repository.\n";
289                 return false;
290         }
291
292         obj->Set(key, val);
293
294         std::cout << "Writing object '" << name << "' to path '" << path << "'.\n";
295
296         //TODO: Create a patch file
297         if(!WriteObjectToRepository(path, name, type, obj)) {
298                 Log(LogCritical, "cli")
299                     << "Can't write object " << name << " to repository.\n";
300                 return false;
301         }
302
303         return true;
304 }
305
306 bool RepositoryUtility::WriteObjectToRepository(const String& path, const String& name, const String& type, const Dictionary::Ptr& item)
307 {
308         Log(LogInformation, "cli", "Dumping config items to file '" + path + "'");
309
310         Utility::MkDirP(Utility::DirName(path), 0755);
311
312         String tempPath = path + ".tmp";
313
314         std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
315         SerializeObject(fp, name, type, item);
316         fp << std::endl;
317         fp.close();
318
319 #ifdef _WIN32
320         _unlink(path.CStr());
321 #endif /* _WIN32 */
322
323         if (rename(tempPath.CStr(), path.CStr()) < 0) {
324                 BOOST_THROW_EXCEPTION(posix_error()
325                     << boost::errinfo_api_function("rename")
326                     << boost::errinfo_errno(errno)
327                     << boost::errinfo_file_name(tempPath));
328         }
329
330         return true;
331 }
332
333 Dictionary::Ptr RepositoryUtility::GetObjectFromRepository(const String& filename)
334 {
335         //TODO: Parse existing configuration objects
336         return Dictionary::Ptr();
337 }
338
339
340 /*
341  * collect functions
342  */
343 std::vector<String> RepositoryUtility::GetObjects(void)
344 {
345         std::vector<String> objects;
346         String path = GetRepositoryConfigPath();
347
348         Utility::GlobRecursive(path, "*.conf",
349             boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile);
350
351         return objects;
352 }
353
354 void RepositoryUtility::CollectObjects(const String& object_file, std::vector<String>& objects)
355 {
356         Log(LogDebug, "cli")
357             << "Adding object: '" << object_file << "'.";
358
359         objects.push_back(object_file);
360 }
361
362
363 bool RepositoryUtility::GetChangeLog(const boost::function<void (const Dictionary::Ptr&, const String&)>& callback)
364 {
365         std::vector<String> changelog;
366         String path = GetRepositoryChangeLogPath() + "/";
367
368         Utility::Glob(path + "/*.change",
369             boost::bind(&RepositoryUtility::CollectChangeLog, _1, boost::ref(changelog)), GlobFile);
370
371         /* sort by timestamp ascending */
372         std::sort(changelog.begin(), changelog.end());
373
374         BOOST_FOREACH(const String& entry, changelog) {
375                 String file = path + entry + ".change";
376                 Dictionary::Ptr change = GetObjectFromRepositoryChangeLog(file);
377
378                 Log(LogDebug, "cli")
379                     << "Collecting entry " << entry << "\n";
380
381                 if (change)
382                         callback(change, file);
383         }
384
385         return true;
386 }
387
388 void RepositoryUtility::CollectChangeLog(const String& change_file, std::vector<String>& changelog)
389 {
390         String file = Utility::BaseName(change_file);
391         boost::algorithm::replace_all(file, ".change", "");
392
393         Log(LogDebug, "cli", "Adding change file: " + file);
394         changelog.push_back(file);
395 }
396
397 void RepositoryUtility::CollectChange(const Dictionary::Ptr& change, Array::Ptr& changes)
398 {
399         changes->Add(change);
400 }
401
402 /*
403  * Commit Changelog
404  */
405 void RepositoryUtility::CommitChange(const Dictionary::Ptr& change, const String& path)
406 {
407         Log(LogDebug, "cli")
408            << "Got change " << change->Get("name");
409
410         String name = change->Get("name");
411         String type = change->Get("type");
412         String command = change->Get("command");
413         Dictionary::Ptr attr;
414
415         if (change->Contains("attr")) {
416                 attr = change->Get("attr");
417         }
418
419         bool success = false;
420
421         if (command == "add") {
422                 success = AddObjectInternal(name, type, attr);
423         }
424         else if (command == "remove") {
425                 success = RemoveObjectInternal(name, type, attr);
426         }
427
428         if (success) {
429                 Log(LogNotice, "cli")
430                     << "Removing changelog file '" << path << "'.";
431                 RemoveObjectFileInternal(path);
432         }
433 }
434
435 /*
436  * Print Changelog helpers
437  */
438 void RepositoryUtility::FormatChangelogEntry(std::ostream& fp, const Dictionary::Ptr& change)
439 {
440         if (!change)
441                 return;
442
443         if (change->Get("command") == "add")
444                 fp << "Adding";
445         if (change->Get("command") == "remove")
446                 fp << "Removing";
447
448         String type = change->Get("type");
449         boost::algorithm::to_lower(type);
450         Dictionary::Ptr attrs = change->Get("attr");
451
452         fp << " " << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << type << ConsoleColorTag(Console_Normal) << " '";
453         fp << ConsoleColorTag(Console_ForegroundBlue | Console_Bold) << change->Get("name") << ConsoleColorTag(Console_Normal) << "'";
454
455         if (!attrs || attrs->GetLength() == 0) {
456                 fp << "\n";
457                 return;
458         }
459
460         fp << " with attributes: \n";
461
462         BOOST_FOREACH(const Dictionary::Pair& kv, attrs) {
463                 /* skip the name */
464                 if (kv.first == "name")
465                         continue;
466
467                 fp << std::setw(4) << " " << ConsoleColorTag(Console_ForegroundGreen) << kv.first << ConsoleColorTag(Console_Normal) << " = ";
468                 FormatValue(fp, kv.second);
469                 fp << "\n";
470         }
471 }
472
473 /*
474  * print helpers for configuration
475  * TODO: Move into a separate class
476  */
477 void RepositoryUtility::SerializeObject(std::ostream& fp, const String& name, const String& type, const Dictionary::Ptr& object)
478 {
479         fp << "object " << type << " \"" << name << "\" {\n";
480
481         if (!object) {
482                 fp << "}\n";
483                 return;
484         }
485
486         if (object->Contains("import")) {
487                 Array::Ptr imports = object->Get("import");
488
489                 ObjectLock olock(imports);
490                 BOOST_FOREACH(const String& import, imports) {
491                         fp << "\t" << "import \"" << import << "\"\n";
492                 }
493         }
494
495         BOOST_FOREACH(const Dictionary::Pair& kv, object) {
496                 if (kv.first == "import" || kv.first == "name") {
497                         continue;
498                 } else {
499                         fp << "\t" << kv.first << " = ";
500                         FormatValue(fp, kv.second);
501                 }
502                 fp << "\n";
503         }
504         fp << "}\n";
505 }
506
507 void RepositoryUtility::FormatValue(std::ostream& fp, const Value& val)
508 {
509         if (val.IsObjectType<Array>()) {
510                 FormatArray(fp, val);
511                 return;
512         }
513
514         if (val.IsString()) {
515                 fp << "\"" << Convert::ToString(val) << "\"";
516                 return;
517         }
518
519         fp << Convert::ToString(val);
520 }
521
522 void RepositoryUtility::FormatArray(std::ostream& fp, const Array::Ptr& arr)
523 {
524         bool first = true;
525
526         fp << "[ ";
527
528         if (arr) {
529                 ObjectLock olock(arr);
530                 BOOST_FOREACH(const Value& value, arr) {
531                         if (first)
532                                 first = false;
533                         else
534                                 fp << ", ";
535
536                         FormatValue(fp, value);
537                 }
538         }
539
540         if (!first)
541                 fp << " ";
542
543         fp << "]";
544 }