]> granicus.if.org Git - icinga2/blob - lib/cli/repositoryutility.cpp
Cli: Delete change file on repository commit
[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 <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::GetRepositoryConfigPath(void)
60 {
61         return Application::GetSysconfDir() + "/icinga2/repository.d";
62 }
63
64 String RepositoryUtility::GetRepositoryObjectConfigPath(const String& type, const Dictionary::Ptr& object)
65 {
66         String path = GetRepositoryConfigPath() + "/";
67
68         if (type == "Host") {
69                 path += "hosts";
70         }
71         else if (type == "Service")
72                 path += "hosts/" + object->Get("host_name");
73         else if (type == "Zone")
74                 path += "zones";
75         else if (type == "Endpoints")
76                 path += "endpoints";
77
78         return path;
79 }
80
81 String RepositoryUtility::GetRepositoryObjectConfigFilePath(const String& type, const Dictionary::Ptr& object)
82 {
83         String path = GetRepositoryObjectConfigPath(type, object);
84
85         path += "/" + object->Get("name") + ".conf";
86
87         return path;
88 }
89
90 String RepositoryUtility::GetRepositoryChangeLogPath(void)
91 {
92         return Application::GetLocalStateDir() + "/lib/icinga2/repository/changes";
93 }
94
95 void RepositoryUtility::PrintObjects(std::ostream& fp, const String& type)
96 {
97         std::vector<String> objects = GetObjects(); //full path
98
99         BOOST_FOREACH(const String& object, objects) {
100                 Dictionary::Ptr obj = GetObjectFromRepository(object);
101
102                 if (obj) {
103                         fp << "Object Name: " << object << "\n";
104                         fp << JsonEncode(obj);
105                 }
106         }
107 }
108
109 /* public interface, only logs changes */
110 bool RepositoryUtility::AddObject(const String& name, const String& type, const Dictionary::Ptr& attr)
111 {
112         /* add a new changelog entry by timestamp */
113         String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
114
115         Dictionary::Ptr change = make_shared<Dictionary>();
116
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);
122
123         return WriteObjectToRepositoryChangeLog(path, change);
124 }
125
126 bool RepositoryUtility::RemoveObject(const String& name, const String& type, const Dictionary::Ptr& attr)
127 {
128         /* add a new changelog entry by timestamp */
129         String path = GetRepositoryChangeLogPath() + "/" + Convert::ToString(static_cast<long>(Utility::GetTime())) + ".change";
130
131         Dictionary::Ptr change = make_shared<Dictionary>();
132
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
138
139         return WriteObjectToRepositoryChangeLog(path, change);
140 }
141
142 bool RepositoryUtility::CommitChangeLog(void)
143 {
144         GetChangeLog(boost::bind(RepositoryUtility::CommitChange, _1, _2));
145
146         return true;
147 }
148
149 void RepositoryUtility::PrintChangeLog(std::ostream& fp)
150 {
151         Array::Ptr changelog = make_shared<Array>();
152
153         GetChangeLog(boost::bind(RepositoryUtility::CollectChange, _1, boost::ref(changelog)));
154
155         ObjectLock olock(changelog);
156
157         std::cout << "Changes to be committed:\n";
158
159         BOOST_FOREACH(const Value& entry, changelog) {
160                 std::cout << JsonEncode(entry) << "\n"; //TODO better formatting
161         }
162 }
163
164 bool RepositoryUtility::SetObjectAttribute(const String& name, const String& type, const String& attr, const Value& val)
165 {
166         //TODO: Implement modification commands
167         return true;
168 }
169
170 /* internal implementation when changes are committed */
171 bool RepositoryUtility::AddObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
172 {
173         String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
174
175         return WriteObjectToRepository(path, name, type, attr);
176 }
177
178 bool RepositoryUtility::RemoveObjectInternal(const String& name, const String& type, const Dictionary::Ptr& attr)
179 {
180         String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
181
182         return RemoveObjectFileInternal(path);
183 }
184
185 bool RepositoryUtility::RemoveObjectFileInternal(const String& path)
186 {
187         if (!Utility::PathExists(path) ) {
188                 Log(LogCritical, "cli", "Cannot remove '" + path + "'. Does not exist.");
189                 return false;
190         }
191
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) + "\".");
195                 return false;
196         }
197
198         return true;
199 }
200
201 bool RepositoryUtility::SetObjectAttributeInternal(const String& name, const String& type, const String& key, const Value& val, const Dictionary::Ptr& attr)
202 {
203         //Fixme
204         String path = GetRepositoryObjectConfigPath(type, attr) + "/" + name + ".conf";
205
206         Dictionary::Ptr obj = GetObjectFromRepository(path);
207
208         if (!obj) {
209                 Log(LogCritical, "cli")
210                     << "Can't get object " << name << " from repository.\n";
211                 return false;
212         }
213
214         obj->Set(key, val);
215
216         std::cout << "Writing object '" << name << "' to path '" << path << "'.\n";
217
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";
222                 return false;
223         }
224
225         return true;
226 }
227
228 bool RepositoryUtility::WriteObjectToRepository(const String& path, const String& name, const String& type, const Dictionary::Ptr& item)
229 {
230         Log(LogInformation, "cli", "Dumping config items to file '" + path + "'");
231
232         Utility::MkDirP(Utility::DirName(path), 0755);
233
234         String tempPath = path + ".tmp";
235
236         std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
237         SerializeObject(fp, name, type, item);
238         fp << std::endl;
239         fp.close();
240
241 #ifdef _WIN32
242         _unlink(path.CStr());
243 #endif /* _WIN32 */
244
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));
250         }
251
252         return true;
253 }
254
255 Dictionary::Ptr RepositoryUtility::GetObjectFromRepository(const String& filename)
256 {
257         //TODO: Parse existing configuration objects
258         return Dictionary::Ptr();
259 }
260
261 bool RepositoryUtility::WriteObjectToRepositoryChangeLog(const String& path, const Dictionary::Ptr& item)
262 {
263         Log(LogInformation, "cli", "Dumping changelog items to file '" + path + "'");
264
265         Utility::MkDirP(Utility::DirName(path), 0750);
266
267         String tempPath = path + ".tmp";
268
269         std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc);
270         fp << JsonEncode(item);
271         fp.close();
272
273 #ifdef _WIN32
274         _unlink(path.CStr());
275 #endif /* _WIN32 */
276
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));
282         }
283
284         return true;
285 }
286
287 Dictionary::Ptr RepositoryUtility::GetObjectFromRepositoryChangeLog(const String& filename)
288 {
289         std::fstream fp;
290         fp.open(filename.CStr(), std::ifstream::in);
291
292         if (!fp)
293                 return Dictionary::Ptr();
294
295         String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
296
297         fp.close();
298
299         return JsonDecode(content);
300 }
301
302 /*
303  * collect functions
304  */
305 std::vector<String> RepositoryUtility::GetObjects(void)
306 {
307         std::vector<String> objects;
308         String path = GetRepositoryConfigPath() + "/";
309
310         Utility::Glob(path + "/*.conf",
311             boost::bind(&RepositoryUtility::CollectObjects, _1, boost::ref(objects)), GlobFile);
312
313         return objects;
314 }
315
316 void RepositoryUtility::CollectObjects(const String& object_file, std::vector<String>& objects)
317 {
318         Log(LogDebug, "cli")
319             << "Adding object: '" << object_file << "'.";
320
321         objects.push_back(object_file);
322 }
323
324
325 bool RepositoryUtility::GetChangeLog(const boost::function<void (const Dictionary::Ptr&, const String&)>& callback)
326 {
327         std::vector<String> changelog;
328         String path = GetRepositoryChangeLogPath() + "/";
329
330         Utility::Glob(path + "/*.change",
331             boost::bind(&RepositoryUtility::CollectChangeLog, _1, boost::ref(changelog)), GlobFile);
332
333         /* sort by timestamp ascending */
334         std::sort(changelog.begin(), changelog.end());
335
336         BOOST_FOREACH(const String& entry, changelog) {
337                 String file = path + entry + ".change";
338                 Dictionary::Ptr change = GetObjectFromRepositoryChangeLog(file);
339
340                 Log(LogInformation, "cli")
341                     << "Collecting entry " << entry << "\n";
342
343                 if (change)
344                         callback(change, file);
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, const String& path)
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         bool success = false;
374
375         if (command == "add") {
376                 success = AddObjectInternal(name, type, attr);
377         }
378         else if (command == "remove") {
379                 success = RemoveObjectInternal(name, type, attr);
380         }
381
382         if (success) {
383                 Log(LogInformation, "cli")
384                     << "Removing changelog file '" << path << "'.";
385                 RemoveObjectFileInternal(path);
386         }
387 }
388
389 void RepositoryUtility::CollectChange(const Dictionary::Ptr& change, Array::Ptr& changes)
390 {
391         changes->Add(change);
392 }
393
394
395 /*
396  * print helpers for configuration
397  * TODO: Move into a separate class
398  */
399 void RepositoryUtility::SerializeObject(std::ostream& fp, const String& name, const String& type, const Dictionary::Ptr& object)
400 {
401         fp << "object " << type << " \"" << name << "\" {\n";
402
403         if (!object) {
404                 fp << "}\n";
405                 return;
406         }
407
408         if (object->Contains("import")) {
409                 Array::Ptr imports = object->Get("import");
410
411                 ObjectLock olock(imports);
412                 BOOST_FOREACH(const String& import, imports) {
413                         fp << "\t" << "import \"" << import << "\"\n";
414                 }
415         }
416
417         BOOST_FOREACH(const Dictionary::Pair& kv, object) {
418                 if (kv.first == "import" || kv.first == "name") {
419                         continue;
420                 } else {
421                         fp << "\t" << kv.first << " = ";
422                         FormatValue(fp, kv.second);
423                 }
424                 fp << "\n";
425         }
426         fp << "}\n";
427 }
428
429 void RepositoryUtility::FormatValue(std::ostream& fp, const Value& val)
430 {
431         if (val.IsObjectType<Array>()) {
432                 FormatArray(fp, val);
433                 return;
434         }
435
436         if (val.IsString()) {
437                 fp << "\"" << Convert::ToString(val) << "\"";
438                 return;
439         }
440
441         fp << Convert::ToString(val);
442 }
443
444 void RepositoryUtility::FormatArray(std::ostream& fp, const Array::Ptr& arr)
445 {
446         bool first = true;
447
448         fp << "[ ";
449
450         if (arr) {
451                 ObjectLock olock(arr);
452                 BOOST_FOREACH(const Value& value, arr) {
453                         if (first)
454                                 first = false;
455                         else
456                                 fp << ", ";
457
458                         FormatValue(fp, value);
459                 }
460         }
461
462         if (!first)
463                 fp << " ";
464
465         fp << "]";
466 }