]> granicus.if.org Git - icinga2/blob - lib/cli/nodeutility.cpp
add some object locking to the Dump method (which could theoreticylly suffer from...
[icinga2] / lib / cli / nodeutility.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "cli/nodeutility.hpp"
4 #include "cli/clicommand.hpp"
5 #include "cli/variableutility.hpp"
6 #include "base/logger.hpp"
7 #include "base/application.hpp"
8 #include "base/tlsutility.hpp"
9 #include "base/convert.hpp"
10 #include "base/utility.hpp"
11 #include "base/scriptglobal.hpp"
12 #include "base/json.hpp"
13 #include "base/netstring.hpp"
14 #include "base/stdiostream.hpp"
15 #include "base/debug.hpp"
16 #include "base/objectlock.hpp"
17 #include "base/console.hpp"
18 #include "base/exception.hpp"
19 #include "base/configwriter.hpp"
20 #include <boost/algorithm/string/join.hpp>
21 #include <boost/algorithm/string/replace.hpp>
22 #include <fstream>
23 #include <iostream>
24
25 using namespace icinga;
26
27 String NodeUtility::GetConstantsConfPath()
28 {
29         return Configuration::ConfigDir + "/constants.conf";
30 }
31
32 String NodeUtility::GetZonesConfPath()
33 {
34         return Configuration::ConfigDir + "/zones.conf";
35 }
36
37 /*
38  * Node Setup helpers
39  */
40
41 int NodeUtility::GenerateNodeIcingaConfig(const String& endpointName, const String& zoneName,
42         const String& parentZoneName, const std::vector<std::string>& endpoints,
43         const std::vector<String>& globalZones)
44 {
45         Array::Ptr config = new Array();
46
47         Array::Ptr myParentZoneMembers = new Array();
48
49         for (const String& endpoint : endpoints) {
50                 /* extract all --endpoint arguments and store host,port info */
51                 std::vector<String> tokens = endpoint.Split(",");
52
53                 Dictionary::Ptr myParentEndpoint = new Dictionary();
54
55                 if (tokens.size() > 1) {
56                         String host = tokens[1].Trim();
57
58                         if (!host.IsEmpty())
59                                 myParentEndpoint->Set("host", host);
60                 }
61
62                 if (tokens.size() > 2) {
63                         String port = tokens[2].Trim();
64
65                         if (!port.IsEmpty())
66                                 myParentEndpoint->Set("port", port);
67                 }
68
69                 String myEndpointName = tokens[0].Trim();
70                 myParentEndpoint->Set("__name", myEndpointName);
71                 myParentEndpoint->Set("__type", "Endpoint");
72
73                 /* save endpoint in master zone */
74                 myParentZoneMembers->Add(myEndpointName);
75
76                 config->Add(myParentEndpoint);
77         }
78
79         /* add the parent zone to the config */
80         config->Add(new Dictionary({
81                 { "__name", parentZoneName },
82                 { "__type", "Zone" },
83                 { "endpoints", myParentZoneMembers }
84         }));
85
86         /* store the local generated node configuration */
87         config->Add(new Dictionary({
88                 { "__name", endpointName },
89                 { "__type", "Endpoint" }
90         }));
91
92         config->Add(new Dictionary({
93                 { "__name", zoneName },
94                 { "__type", "Zone" },
95                 { "parent", parentZoneName },
96                 { "endpoints", new Array({ endpointName }) }
97         }));
98
99         for (const String& globalzone : globalZones) {
100                 config->Add(new Dictionary({
101                         { "__name", globalzone },
102                         { "__type", "Zone" },
103                         { "global", true }
104                 }));
105         }
106
107         /* Write the newly generated configuration. */
108         NodeUtility::WriteNodeConfigObjects(GetZonesConfPath(), config);
109
110         return 0;
111 }
112
113 int NodeUtility::GenerateNodeMasterIcingaConfig(const String& endpointName, const String& zoneName,
114         const std::vector<String>& globalZones)
115 {
116         Array::Ptr config = new Array();
117
118         /* store the local generated node master configuration */
119         config->Add(new Dictionary({
120                 { "__name", endpointName },
121                 { "__type", "Endpoint" }
122         }));
123
124         config->Add(new Dictionary({
125                 { "__name", zoneName },
126                 { "__type", "Zone" },
127                 { "endpoints", new Array({ endpointName }) }
128         }));
129
130         for (const String& globalzone : globalZones) {
131                 config->Add(new Dictionary({
132                         { "__name", globalzone },
133                         { "__type", "Zone" },
134                         { "global", true }
135                 }));
136         }
137
138         /* Write the newly generated configuration. */
139         NodeUtility::WriteNodeConfigObjects(GetZonesConfPath(), config);
140
141         return 0;
142 }
143
144 bool NodeUtility::WriteNodeConfigObjects(const String& filename, const Array::Ptr& objects)
145 {
146         Log(LogInformation, "cli")
147                 << "Dumping config items to file '" << filename << "'.";
148
149         /* create a backup first */
150         CreateBackupFile(filename);
151
152         String path = Utility::DirName(filename);
153
154         Utility::MkDirP(path, 0755);
155
156         String user = Configuration::RunAsUser;
157         String group = Configuration::RunAsGroup;
158
159         if (!Utility::SetFileOwnership(path, user, group)) {
160                 Log(LogWarning, "cli")
161                         << "Cannot set ownership for user '" << user << "' group '" << group << "' on path '" << path << "'. Verify it yourself!";
162         }
163         if (!Utility::SetFileOwnership(filename, user, group)) {
164                 Log(LogWarning, "cli")
165                         << "Cannot set ownership for user '" << user << "' group '" << group << "' on path '" << path << "'. Verify it yourself!";
166         }
167
168         std::fstream fp;
169         String tempFilename = Utility::CreateTempFile(filename + ".XXXXXX", 0644, fp);
170
171         fp << "/*\n";
172         fp << " * Generated by Icinga 2 node setup commands\n";
173         fp << " * on " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n";
174         fp << " */\n\n";
175
176         ObjectLock olock(objects);
177         for (const Dictionary::Ptr& object : objects) {
178                 SerializeObject(fp, object);
179         }
180
181         fp << std::endl;
182         fp.close();
183
184 #ifdef _WIN32
185         _unlink(filename.CStr());
186 #endif /* _WIN32 */
187
188         if (rename(tempFilename.CStr(), filename.CStr()) < 0) {
189                 BOOST_THROW_EXCEPTION(posix_error()
190                         << boost::errinfo_api_function("rename")
191                         << boost::errinfo_errno(errno)
192                         << boost::errinfo_file_name(tempFilename));
193         }
194
195         return true;
196 }
197
198
199 /*
200  * We generally don't overwrite files without backup before
201  */
202 bool NodeUtility::CreateBackupFile(const String& target, bool isPrivate)
203 {
204         if (!Utility::PathExists(target))
205                 return false;
206
207         String backup = target + ".orig";
208
209         if (Utility::PathExists(backup)) {
210                 Log(LogInformation, "cli")
211                         << "Backup file '" << backup << "' already exists. Skipping backup.";
212                 return false;
213         }
214
215         Utility::CopyFile(target, backup);
216
217 #ifndef _WIN32
218         if (isPrivate)
219                 chmod(backup.CStr(), 0600);
220 #endif /* _WIN32 */
221
222         Log(LogInformation, "cli")
223                 << "Created backup file '" << backup << "'.";
224
225         return true;
226 }
227
228 void NodeUtility::SerializeObject(std::ostream& fp, const Dictionary::Ptr& object)
229 {
230         fp << "object ";
231         ConfigWriter::EmitIdentifier(fp, object->Get("__type"), false);
232         fp << " ";
233         ConfigWriter::EmitValue(fp, 0, object->Get("__name"));
234         fp << " {\n";
235
236         ObjectLock olock(object);
237         for (const Dictionary::Pair& kv : object) {
238                 if (kv.first == "__type" || kv.first == "__name")
239                         continue;
240
241                 fp << "\t";
242                 ConfigWriter::EmitIdentifier(fp, kv.first, true);
243                 fp << " = ";
244                 ConfigWriter::EmitValue(fp, 1, kv.second);
245                 fp << "\n";
246         }
247
248         fp << "}\n\n";
249 }
250
251 /*
252 * Returns true if the include is found, otherwise false
253 */
254 bool NodeUtility::GetConfigurationIncludeState(const String& value, bool recursive) {
255         String configurationFile = Configuration::ConfigDir + "/icinga2.conf";
256
257         Log(LogInformation, "cli")
258                 << "Reading '" << configurationFile << "'.";
259
260         std::ifstream ifp(configurationFile.CStr());
261
262         String affectedInclude = value;
263
264         if (recursive)
265                 affectedInclude = "include_recursive " + affectedInclude;
266         else
267                 affectedInclude = "include " + affectedInclude;
268
269         bool isIncluded = false;
270
271         std::string line;
272
273         while(std::getline(ifp, line)) {
274                 /*
275                 * Trying to find if the inclusion is enabled.
276                 * First hit breaks out of the loop.
277                 */
278
279                 if (line.compare(0, affectedInclude.GetLength(), affectedInclude) == 0) {
280                         isIncluded = true;
281
282                         /*
283                         * We can safely break out here, since an enabled include always win.
284                         */
285                         break;
286                 }
287         }
288
289         ifp.close();
290
291         return isIncluded;
292 }
293
294 /*
295  * include = false, will comment out the include statement
296  * include = true, will add an include statement or uncomment a statement if one is existing
297  * resursive = false, will search for a non-resursive include statement
298  * recursive = true, will search for a resursive include statement
299  * Returns true on success, false if option was not found
300  */
301 bool NodeUtility::UpdateConfiguration(const String& value, bool include, bool recursive)
302 {
303         String configurationFile = Configuration::ConfigDir + "/icinga2.conf";
304
305         Log(LogInformation, "cli")
306                 << "Updating '" << value << "' include in '" << configurationFile << "'.";
307
308         NodeUtility::CreateBackupFile(configurationFile);
309
310         std::ifstream ifp(configurationFile.CStr());
311         std::fstream ofp;
312         String tempFile = Utility::CreateTempFile(configurationFile + ".XXXXXX", 0644, ofp);
313
314         String affectedInclude = value;
315
316         if (recursive)
317                 affectedInclude = "include_recursive " + affectedInclude;
318         else
319                 affectedInclude = "include " + affectedInclude;
320
321         bool found = false;
322
323         std::string line;
324
325         while (std::getline(ifp, line)) {
326                 if (include) {
327                         if (line.find("//" + affectedInclude) != std::string::npos || line.find("// " + affectedInclude) != std::string::npos) {
328                                 found = true;
329                                 ofp << "// Added by the node setup CLI command on "
330                                         << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime())
331                                         << "\n" + affectedInclude + "\n";
332                         } else if (line.find(affectedInclude) != std::string::npos) {
333                                 found = true;
334
335                                 Log(LogInformation, "cli")
336                                         << "Include statement '" + affectedInclude + "' already set.";
337
338                                 ofp << line << "\n";
339                         } else {
340                                 ofp << line << "\n";
341                         }
342                 } else {
343                         if (line.find(affectedInclude) != std::string::npos) {
344                                 found = true;
345                                 ofp << "// Disabled by the node setup CLI command on "
346                                         << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime())
347                                         << "\n// " + affectedInclude + "\n";
348                         } else {
349                                 ofp << line << "\n";
350                         }
351                 }
352         }
353
354         if (include && !found) {
355                 ofp << "// Added by the node setup CLI command on "
356                         << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime())
357                         << "\n" + affectedInclude + "\n";
358         }
359
360         ifp.close();
361         ofp.close();
362
363 #ifdef _WIN32
364         _unlink(configurationFile.CStr());
365 #endif /* _WIN32 */
366
367         if (rename(tempFile.CStr(), configurationFile.CStr()) < 0) {
368                 BOOST_THROW_EXCEPTION(posix_error()
369                         << boost::errinfo_api_function("rename")
370                         << boost::errinfo_errno(errno)
371                         << boost::errinfo_file_name(configurationFile));
372         }
373
374         return (found || include);
375 }
376
377 void NodeUtility::UpdateConstant(const String& name, const String& value)
378 {
379         String constantsConfPath = NodeUtility::GetConstantsConfPath();
380
381         Log(LogInformation, "cli")
382                 << "Updating '" << name << "' constant in '" << constantsConfPath << "'.";
383
384         NodeUtility::CreateBackupFile(constantsConfPath);
385
386         std::ifstream ifp(constantsConfPath.CStr());
387         std::fstream ofp;
388         String tempFile = Utility::CreateTempFile(constantsConfPath + ".XXXXXX", 0644, ofp);
389
390         bool found = false;
391
392         std::string line;
393         while (std::getline(ifp, line)) {
394                 if (line.find("const " + name + " = ") != std::string::npos) {
395                         ofp << "const " + name + " = \"" + value + "\"\n";
396                         found = true;
397                 } else
398                         ofp << line << "\n";
399         }
400
401         if (!found)
402                 ofp << "const " + name + " = \"" + value + "\"\n";
403
404         ifp.close();
405         ofp.close();
406
407 #ifdef _WIN32
408         _unlink(constantsConfPath.CStr());
409 #endif /* _WIN32 */
410
411         if (rename(tempFile.CStr(), constantsConfPath.CStr()) < 0) {
412                 BOOST_THROW_EXCEPTION(posix_error()
413                         << boost::errinfo_api_function("rename")
414                         << boost::errinfo_errno(errno)
415                         << boost::errinfo_file_name(constantsConfPath));
416         }
417 }