1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
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>
25 using namespace icinga;
27 String NodeUtility::GetConstantsConfPath()
29 return Configuration::ConfigDir + "/constants.conf";
32 String NodeUtility::GetZonesConfPath()
34 return Configuration::ConfigDir + "/zones.conf";
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)
45 Array::Ptr config = new Array();
47 Array::Ptr myParentZoneMembers = new Array();
49 for (const String& endpoint : endpoints) {
50 /* extract all --endpoint arguments and store host,port info */
51 std::vector<String> tokens = endpoint.Split(",");
53 Dictionary::Ptr myParentEndpoint = new Dictionary();
55 if (tokens.size() > 1) {
56 String host = tokens[1].Trim();
59 myParentEndpoint->Set("host", host);
62 if (tokens.size() > 2) {
63 String port = tokens[2].Trim();
66 myParentEndpoint->Set("port", port);
69 String myEndpointName = tokens[0].Trim();
70 myParentEndpoint->Set("__name", myEndpointName);
71 myParentEndpoint->Set("__type", "Endpoint");
73 /* save endpoint in master zone */
74 myParentZoneMembers->Add(myEndpointName);
76 config->Add(myParentEndpoint);
79 /* add the parent zone to the config */
80 config->Add(new Dictionary({
81 { "__name", parentZoneName },
83 { "endpoints", myParentZoneMembers }
86 /* store the local generated node configuration */
87 config->Add(new Dictionary({
88 { "__name", endpointName },
89 { "__type", "Endpoint" }
92 config->Add(new Dictionary({
93 { "__name", zoneName },
95 { "parent", parentZoneName },
96 { "endpoints", new Array({ endpointName }) }
99 for (const String& globalzone : globalZones) {
100 config->Add(new Dictionary({
101 { "__name", globalzone },
102 { "__type", "Zone" },
107 /* Write the newly generated configuration. */
108 NodeUtility::WriteNodeConfigObjects(GetZonesConfPath(), config);
113 int NodeUtility::GenerateNodeMasterIcingaConfig(const String& endpointName, const String& zoneName,
114 const std::vector<String>& globalZones)
116 Array::Ptr config = new Array();
118 /* store the local generated node master configuration */
119 config->Add(new Dictionary({
120 { "__name", endpointName },
121 { "__type", "Endpoint" }
124 config->Add(new Dictionary({
125 { "__name", zoneName },
126 { "__type", "Zone" },
127 { "endpoints", new Array({ endpointName }) }
130 for (const String& globalzone : globalZones) {
131 config->Add(new Dictionary({
132 { "__name", globalzone },
133 { "__type", "Zone" },
138 /* Write the newly generated configuration. */
139 NodeUtility::WriteNodeConfigObjects(GetZonesConfPath(), config);
144 bool NodeUtility::WriteNodeConfigObjects(const String& filename, const Array::Ptr& objects)
146 Log(LogInformation, "cli")
147 << "Dumping config items to file '" << filename << "'.";
149 /* create a backup first */
150 CreateBackupFile(filename);
152 String path = Utility::DirName(filename);
154 Utility::MkDirP(path, 0755);
156 String user = Configuration::RunAsUser;
157 String group = Configuration::RunAsGroup;
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!";
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!";
169 String tempFilename = Utility::CreateTempFile(filename + ".XXXXXX", 0644, fp);
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";
176 ObjectLock olock(objects);
177 for (const Dictionary::Ptr& object : objects) {
178 SerializeObject(fp, object);
185 _unlink(filename.CStr());
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));
200 * We generally don't overwrite files without backup before
202 bool NodeUtility::CreateBackupFile(const String& target, bool isPrivate)
204 if (!Utility::PathExists(target))
207 String backup = target + ".orig";
209 if (Utility::PathExists(backup)) {
210 Log(LogInformation, "cli")
211 << "Backup file '" << backup << "' already exists. Skipping backup.";
215 Utility::CopyFile(target, backup);
219 chmod(backup.CStr(), 0600);
222 Log(LogInformation, "cli")
223 << "Created backup file '" << backup << "'.";
228 void NodeUtility::SerializeObject(std::ostream& fp, const Dictionary::Ptr& object)
231 ConfigWriter::EmitIdentifier(fp, object->Get("__type"), false);
233 ConfigWriter::EmitValue(fp, 0, object->Get("__name"));
236 ObjectLock olock(object);
237 for (const Dictionary::Pair& kv : object) {
238 if (kv.first == "__type" || kv.first == "__name")
242 ConfigWriter::EmitIdentifier(fp, kv.first, true);
244 ConfigWriter::EmitValue(fp, 1, kv.second);
252 * Returns true if the include is found, otherwise false
254 bool NodeUtility::GetConfigurationIncludeState(const String& value, bool recursive) {
255 String configurationFile = Configuration::ConfigDir + "/icinga2.conf";
257 Log(LogInformation, "cli")
258 << "Reading '" << configurationFile << "'.";
260 std::ifstream ifp(configurationFile.CStr());
262 String affectedInclude = value;
265 affectedInclude = "include_recursive " + affectedInclude;
267 affectedInclude = "include " + affectedInclude;
269 bool isIncluded = false;
273 while(std::getline(ifp, line)) {
275 * Trying to find if the inclusion is enabled.
276 * First hit breaks out of the loop.
279 if (line.compare(0, affectedInclude.GetLength(), affectedInclude) == 0) {
283 * We can safely break out here, since an enabled include always win.
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
301 bool NodeUtility::UpdateConfiguration(const String& value, bool include, bool recursive)
303 String configurationFile = Configuration::ConfigDir + "/icinga2.conf";
305 Log(LogInformation, "cli")
306 << "Updating '" << value << "' include in '" << configurationFile << "'.";
308 NodeUtility::CreateBackupFile(configurationFile);
310 std::ifstream ifp(configurationFile.CStr());
312 String tempFile = Utility::CreateTempFile(configurationFile + ".XXXXXX", 0644, ofp);
314 String affectedInclude = value;
317 affectedInclude = "include_recursive " + affectedInclude;
319 affectedInclude = "include " + affectedInclude;
325 while (std::getline(ifp, line)) {
327 if (line.find("//" + affectedInclude) != std::string::npos || line.find("// " + affectedInclude) != std::string::npos) {
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) {
335 Log(LogInformation, "cli")
336 << "Include statement '" + affectedInclude + "' already set.";
343 if (line.find(affectedInclude) != std::string::npos) {
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";
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";
364 _unlink(configurationFile.CStr());
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));
374 return (found || include);
377 void NodeUtility::UpdateConstant(const String& name, const String& value)
379 String constantsConfPath = NodeUtility::GetConstantsConfPath();
381 Log(LogInformation, "cli")
382 << "Updating '" << name << "' constant in '" << constantsConfPath << "'.";
384 NodeUtility::CreateBackupFile(constantsConfPath);
386 std::ifstream ifp(constantsConfPath.CStr());
388 String tempFile = Utility::CreateTempFile(constantsConfPath + ".XXXXXX", 0644, ofp);
393 while (std::getline(ifp, line)) {
394 if (line.find("const " + name + " = ") != std::string::npos) {
395 ofp << "const " + name + " = \"" + value + "\"\n";
402 ofp << "const " + name + " = \"" + value + "\"\n";
408 _unlink(constantsConfPath.CStr());
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));