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