]> granicus.if.org Git - icinga2/blob - lib/cli/repositoryutility.cpp
Cli: Parse repository arguments as 'name=...'
[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/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>
33 #include <boost/algorithm/string/replace.hpp>
34 #include <boost/algorithm/string/split.hpp>
35 #include <boost/algorithm/string/classification.hpp>
36 #include <fstream>
37 #include <iostream>
38
39 using namespace icinga;
40
41 Dictionary::Ptr RepositoryUtility::GetArgumentAttributes(const std::vector<std::string>& arguments)
42 {
43         Dictionary::Ptr attr = make_shared<Dictionary>();
44
45         BOOST_FOREACH(const String& kv, arguments) {
46                 std::vector<String> tokens;
47                 boost::algorithm::split(tokens, kv, boost::is_any_of("="));
48
49                 if (tokens.size() == 2) {
50                         attr->Set(tokens[0], tokens[1]);
51                 } else
52                         Log(LogWarning, "cli")
53                             << "Cannot parse passed attributes: " << boost::algorithm::join(tokens, "=");
54         }
55
56         return attr;
57 }
58
59 String RepositoryUtility::GetRepositoryDPath(void)
60 {
61         return Application::GetSysconfDir() + "/icinga2/repository.d";
62 }
63
64 String RepositoryUtility::GetRepositoryDObjectsPath(const String& type, const String& hostname)
65 {
66         //TODO find a better way to retrieve the objects path
67         if (type == "Host")
68                 return GetRepositoryDPath() + "/hosts";
69         else if (type == "Service")
70                 return GetRepositoryDPath() + "/hosts/" + hostname;
71         else if (type == "Zone")
72                 return GetRepositoryDPath() + "/zones";
73         else if (type == "Endpoints")
74                 return GetRepositoryDPath() + "/endpoints";
75         else
76                 return GetRepositoryDPath();
77 }
78
79 String RepositoryUtility::GetRepositoryChangeLogPath(void)
80 {
81         return Application::GetLocalStateDir() + "/lib/icinga2/repository";
82 }
83
84 void RepositoryUtility::PrintObjects(std::ostream& fp, const String& type)
85 {
86         std::vector<String> objects;
87         GetObjects(type, objects);
88
89         BOOST_FOREACH(const String& object, objects) {
90                 Dictionary::Ptr obj = GetObjectFromRepository(GetRepositoryDObjectsPath(type) + "/" + object + ".conf");
91
92                 if (obj) {
93                         fp << "Object Name: " << object << "\n";
94                         fp << JsonSerialize(obj);
95                 }
96         }
97 }
98
99 /* public interface, only logs changes */
100 bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attr)
101 {
102         /* add a new changelog entry by timestamp */
103         String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
104
105         Dictionary::Ptr change = make_shared<Dictionary>();
106
107         change->Set("timestamp", Utility::GetTime());
108         change->Set("name", name);
109         change->Set("type", type);
110         change->Set("command", "add");
111         change->Set("attr", attr);
112
113         return WriteObjectToRepositoryChangeLog(path, change);
114 }
115
116 bool RepositoryUtility::RemoveObject(const String& name, const String& type)
117 {
118         /* add a new changelog entry by timestamp */
119         String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
120
121         Dictionary::Ptr change = make_shared<Dictionary>();
122
123         change->Set("timestamp", Utility::GetTime());
124         change->Set("name", name);
125         change->Set("type", type);
126         change->Set("command", "remove");
127
128         return WriteObjectToRepositoryChangeLog(path, change);
129 }
130
131 bool RepositoryUtility::CommitChangeLog(void)
132 {
133         GetChangeLog(boost::bind(RepositoryUtility::CommitChange, _1));
134
135         return true;
136 }
137
138 void RepositoryUtility::PrintChangeLog(std::ostream& fp)
139 {
140         Array::Ptr changelog = make_shared<Array>();
141
142         GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
143
144         ObjectLock olock(changelog);
145
146         std::cout << "Changes to be committed:\n";
147
148         BOOST_FOREACH(const Value& entry, changelog) {
149                 std::cout << JsonSerialize(entry) << "\n"; //TODO better formatting
150         }
151 }
152
153 bool RepositoryUtility::SetObjectAttribute(const String& name, const String& type, const String& attr, const Value& val)
154 {
155         //TODO: Implement modification commands
156         return true;
157 }
158
159 /* internal implementation when changes are committed */
160 bool RepositoryUtility::AddObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
161 {
162         String path = GetRepositoryDObjectsPath(type, name) + "/" + name + ".conf";
163
164         return WriteObjectToRepository(path, name, type, attr);
165 }
166
167 bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type)
168 {
169         String path = GetRepositoryDObjectsPath(type, name) + "/" + name + ".conf";
170
171         return RemoveObjectFileInternal(path);
172 }
173
174 bool RepositoryUtility::RemoveObjectFileInternal(const String& path)
175 {
176         if (!Utility::PathExists(path) ) {
177                 Log(LogCritical, "cli", "Cannot remove '" + path + "'. Does not exist.");
178                 return false;
179         }
180
181         if (unlink(path.CStr()) < 0) {
182                 Log(LogCritical, "cli", "Cannot remove file '" + path +
183                     "'. Failed with error code " + Convert::ToString(errno) + ", \"" + Utility::FormatErrorNumber(errno) + "\".");
184                 return false;
185         }
186
187         return true;
188 }
189
190 bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& attr, const Value& val)
191 {
192         //Fixme
193         String path = GetRepositoryDObjectsPath(type, name) + "/" + name + ".conf";
194
195         Dictionary::Ptr obj = GetObjectFromRepository(path);
196
197         if (!obj) {
198                 Log(LogCritical, "cli")
199                     << "Can't get object " << name << " from repository.\n";
200                 return false;
201         }
202
203         obj->Set(attr, val);
204
205         std::cout << "Writing object '" << name << "' to path '" << path << "'.\n";
206
207         //TODO: Create a patch file
208         if(!WriteObjectToRepository(path, name, type, obj)) {
209                 Log(LogCritical, "cli")
210                     << "Can't write object " << name << " to repository.\n";
211                 return false;
212         }
213
214         return true;
215 }
216
217 bool RepositoryUtility::WriteObjectToRepository(const String& path, const String& name, const String& type, const Dictionary::Ptr& item)
218 {
219         Log(LogInformation, "cli", "Dumping config items to file '" + path + "'");
220
221         Utility::MkDirP(Utility::DirName(path), 0755);
222
223         String tempPath = path + ".tmp";
224
225         std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
226         SerializeObject(fp, name, type, item);
227         fp << std::endl;
228         fp.close();
229
230 #ifdef _WIN32
231         _unlink(path.CStr());
232 #endif /* _WIN32 */
233
234         if (rename(tempPath.CStr(), path.CStr()) < 0) {
235                 BOOST_THROW_EXCEPTION(posix_error()
236                     << boost::errinfo_api_function("rename")
237                     << boost::errinfo_errno(errno)
238                     << boost::errinfo_file_name(tempPath));
239         }
240
241         return true;
242 }
243
244 Dictionary::Ptr RepositoryUtility::GetObjectFromRepository(const String& filename)
245 {
246         //TODO: Parse existing configuration objects
247         return Dictionary::Ptr();
248 }
249
250 bool RepositoryUtility::WriteObjectToRepositoryChangeLog(const String& path, const Dictionary::Ptr& item)
251 {
252         Log(LogInformation, "cli", "Dumping changelog items to file '" + path + "'");
253
254         Utility::MkDirP(Utility::DirName(path), 0755);
255
256         String tempPath = path + ".tmp";
257
258         std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
259         fp << JsonSerialize(item);
260         fp.close();
261
262 #ifdef _WIN32
263         _unlink(path.CStr());
264 #endif /* _WIN32 */
265
266         if (rename(tempPath.CStr(), path.CStr()) < 0) {
267                 BOOST_THROW_EXCEPTION(posix_error()
268                     << boost::errinfo_api_function("rename")
269                     << boost::errinfo_errno(errno)
270                     << boost::errinfo_file_name(tempPath));
271         }
272
273         return true;
274 }
275
276 Dictionary::Ptr RepositoryUtility::GetObjectFromRepositoryChangeLog(const String& filename)
277 {
278         std::fstream fp;
279         fp.open(filename.CStr(), std::ifstream::in);
280
281         if (!fp)
282                 return Dictionary::Ptr();
283
284         String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
285
286         fp.close();
287
288         return JsonDeserialize(content);
289 }
290
291
292 /*
293  * collect functions
294  */
295 bool RepositoryUtility::GetObjects(const String& type, std::vector<String>& objects)
296 {
297         String path = GetRepositoryDPath() + "/";
298
299         if (type == "service")
300                 path = "hosts/*";
301         else
302                 path = type;
303
304         if (!Utility::Glob(path + "/*.conf",
305             boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile)) {
306                 Log(LogCritical, "cli", "Cannot access path '" + path + "'.");
307                 return false;
308         }
309
310         return true;
311 }
312
313 void RepositoryUtility::CollectObjects(const String& object_file, std::vector<String>& objects)
314 {
315         String object = Utility::BaseName(object_file);
316         boost::algorithm::replace_all(object, ".conf", "");
317
318         Log(LogDebug, "cli", "Adding object: " + object);
319         objects.push_back(object);
320 }
321
322
323 bool RepositoryUtility::GetChangeLog(const boost::function<void (const Dictionary::Ptr&)>& callback)
324 {
325         std::vector<String> changelog;
326         String path = GetRepositoryChangeLogPath() + "/";
327
328         if (!Utility::Glob(path + "/*.change",
329             boost::bind(&RepositoryUtility::CollectChangeLog, _1, boost::ref(changelog)), GlobFile)) {
330                 Log(LogCritical, "cli", "Cannot access path '" + path + "'.");
331                 return false;
332         }
333
334         /* sort by timestamp ascending */
335         std::sort(changelog.begin(), changelog.end());
336
337         BOOST_FOREACH(const String& entry, changelog) {
338                 Dictionary::Ptr change = GetObjectFromRepositoryChangeLog(path + entry + ".change");
339
340                 Log(LogInformation, "cli")
341                     << "Collecting entry " << entry << "\n";
342
343                 if (change)
344                         callback(change);
345         }
346
347         return true;
348 }
349
350 void RepositoryUtility::CollectChangeLog(const String& change_file, std::vector<String>& changelog)
351 {
352         String file = Utility::BaseName(change_file);
353         boost::algorithm::replace_all(file, ".change", "");
354
355         Log(LogDebug, "cli", "Adding change file: " + file);
356         changelog.push_back(file);
357 }
358
359 void RepositoryUtility::CommitChange(const Dictionary::Ptr& change)
360 {
361         Log(LogInformation, "cli")
362            << "Got change " << change->Get("name");
363
364         String name = change->Get("name");
365         String type = change->Get("type");
366         String command = change->Get("command");
367         Dictionary::Ptr attr;
368
369         if (change->Contains("attr")) {
370                 attr = change->Get("attr");
371         }
372
373         if (command == "add") {
374                 AddObjectInternal(name, type, attr);
375         }
376         else if (command == "remove") {
377                 RemoveObjectInternal(name, type);
378         }
379 }
380
381 void RepositoryUtility::CollectChange(const Dictionary::Ptr& change, Array::Ptr& changes)
382 {
383         changes->Add(change);
384 }
385
386
387 /*
388  * print helpers for configuration
389  * TODO: Move into a separate class
390  */
391 void RepositoryUtility::SerializeObject(std::ostream& fp, const String& name, const String& type, const Dictionary::Ptr& object)
392 {
393         fp << "object " << type << " \"" << name << "\" {\n";
394
395         if (!object) {
396                 fp << "}\n";
397                 return;
398         }
399
400         if (object->Contains("import")) {
401                 Array::Ptr imports = object->Get("import");
402
403                 ObjectLock olock(imports);
404                 BOOST_FOREACH(const String& import, imports) {
405                         fp << "\t" << "import \"" << import << "\"\n";
406                 }
407         }
408
409         BOOST_FOREACH(const Dictionary::Pair& kv, object) {
410                 if (kv.first == "import") {
411                         continue;
412                 } else {
413                         fp << "\t" << kv.first << " = ";
414                         FormatValue(fp, kv.second);
415                 }
416                 fp << "\n";
417         }
418         fp << "}\n";
419 }
420
421 void RepositoryUtility::FormatValue(std::ostream& fp, const Value& val)
422 {
423         if (val.IsObjectType<Array>()) {
424                 FormatArray(fp, val);
425                 return;
426         }
427
428         if (val.IsString()) {
429                 fp << "\"" << Convert::ToString(val) << "\"";
430                 return;
431         }
432
433         fp << Convert::ToString(val);
434 }
435
436 void RepositoryUtility::FormatArray(std::ostream& fp, const Array::Ptr& arr)
437 {
438         bool first = true;
439
440         fp << "[ ";
441
442         if (arr) {
443                 ObjectLock olock(arr);
444                 BOOST_FOREACH(const Value& value, arr) {
445                         if (first)
446                                 first = false;
447                         else
448                                 fp << ", ";
449
450                         FormatValue(fp, value);
451                 }
452         }
453
454         if (!first)
455                 fp << " ";
456
457         fp << "]";
458 }