1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
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. *
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. *
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 ******************************************************************************/
20 #include "cli/nodeutility.hpp"
21 #include "cli/clicommand.hpp"
22 #include "cli/variableutility.hpp"
23 #include "base/logger.hpp"
24 #include "base/application.hpp"
25 #include "base/tlsutility.hpp"
26 #include "base/convert.hpp"
27 #include "base/utility.hpp"
28 #include "base/scriptglobal.hpp"
29 #include "base/json.hpp"
30 #include "base/netstring.hpp"
31 #include "base/stdiostream.hpp"
32 #include "base/debug.hpp"
33 #include "base/objectlock.hpp"
34 #include "base/console.hpp"
35 #include "base/exception.hpp"
36 #include "base/configwriter.hpp"
37 #include <boost/algorithm/string/join.hpp>
38 #include <boost/algorithm/string/replace.hpp>
42 using namespace icinga;
44 String NodeUtility::GetConstantsConfPath()
46 return Application::GetSysconfDir() + "/icinga2/constants.conf";
53 int NodeUtility::GenerateNodeIcingaConfig(const std::vector<std::string>& endpoints, const std::vector<String>& globalZones)
55 Array::Ptr my_config = new Array();
57 Array::Ptr my_master_zone_members = new Array();
59 String master_zone_name = "master"; //TODO: Find a better name.
61 for (const String& endpoint : endpoints) {
62 /* extract all --endpoint arguments and store host,port info */
63 std::vector<String> tokens = endpoint.Split(",");
65 Dictionary::Ptr my_master_endpoint = new Dictionary();
67 if (tokens.size() > 1) {
68 String host = tokens[1].Trim();
71 my_master_endpoint->Set("host", host);
74 if (tokens.size() > 2) {
75 String port = tokens[2].Trim();
78 my_master_endpoint->Set("port", port);
81 String cn = tokens[0].Trim();
82 my_master_endpoint->Set("__name", cn);
83 my_master_endpoint->Set("__type", "Endpoint");
85 /* save endpoint in master zone */
86 my_master_zone_members->Add(cn);
88 my_config->Add(my_master_endpoint);
91 /* add the master zone to the config */
92 my_config->Add(new Dictionary({
93 { "__name", master_zone_name },
95 { "endpoints", my_master_zone_members }
98 /* store the local generated node configuration */
99 my_config->Add(new Dictionary({
100 { "__name", new ConfigIdentifier("NodeName") },
101 { "__type", "Endpoint" }
104 my_config->Add(new Dictionary({
105 { "__name", new ConfigIdentifier("ZoneName") },
106 { "__type", "Zone" },
107 { "parent", master_zone_name }, //set the master zone as parent
108 { "endpoints", new Array({ new ConfigIdentifier("ZoneName") }) }
111 for (const String& globalzone : globalZones) {
112 my_config->Add(new Dictionary({
113 { "__name", globalzone },
114 { "__type", "Zone" },
119 /* write the newly generated configuration */
120 String zones_path = Application::GetSysconfDir() + "/icinga2/zones.conf";
122 NodeUtility::WriteNodeConfigObjects(zones_path, my_config);
127 int NodeUtility::GenerateNodeMasterIcingaConfig(const std::vector<String>& globalZones)
129 Array::Ptr my_config = new Array();
131 /* store the local generated node master configuration */
132 my_config->Add(new Dictionary({
133 { "__name", new ConfigIdentifier("NodeName") },
134 { "__type", "Endpoint" }
137 my_config->Add(new Dictionary({
138 { "__name", new ConfigIdentifier("ZoneName") },
139 { "__type", "Zone" },
140 { "endpoints", new Array({ new ConfigIdentifier("NodeName") }) }
143 for (const String& globalzone : globalZones) {
144 my_config->Add(new Dictionary({
145 { "__name", globalzone },
146 { "__type", "Zone" },
151 /* write the newly generated configuration */
152 String zones_path = Application::GetSysconfDir() + "/icinga2/zones.conf";
154 NodeUtility::WriteNodeConfigObjects(zones_path, my_config);
159 bool NodeUtility::WriteNodeConfigObjects(const String& filename, const Array::Ptr& objects)
161 Log(LogInformation, "cli")
162 << "Dumping config items to file '" << filename << "'.";
164 /* create a backup first */
165 CreateBackupFile(filename);
167 String path = Utility::DirName(filename);
169 Utility::MkDirP(path, 0755);
171 String user = ScriptGlobal::Get("RunAsUser");
172 String group = ScriptGlobal::Get("RunAsGroup");
174 if (!Utility::SetFileOwnership(path, user, group)) {
175 Log(LogWarning, "cli")
176 << "Cannot set ownership for user '" << user << "' group '" << group << "' on path '" << path << "'. Verify it yourself!";
178 if (!Utility::SetFileOwnership(filename, user, group)) {
179 Log(LogWarning, "cli")
180 << "Cannot set ownership for user '" << user << "' group '" << group << "' on path '" << path << "'. Verify it yourself!";
184 String tempFilename = Utility::CreateTempFile(filename + ".XXXXXX", 0644, fp);
187 fp << " * Generated by Icinga 2 node setup commands\n";
188 fp << " * on " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", Utility::GetTime()) << "\n";
191 ObjectLock olock(objects);
192 for (const Dictionary::Ptr& object : objects) {
193 SerializeObject(fp, object);
200 _unlink(filename.CStr());
203 if (rename(tempFilename.CStr(), filename.CStr()) < 0) {
204 BOOST_THROW_EXCEPTION(posix_error()
205 << boost::errinfo_api_function("rename")
206 << boost::errinfo_errno(errno)
207 << boost::errinfo_file_name(tempFilename));
215 * We generally don't overwrite files without backup before
217 bool NodeUtility::CreateBackupFile(const String& target, bool is_private)
219 if (!Utility::PathExists(target))
222 String backup = target + ".orig";
224 if (Utility::PathExists(backup)) {
225 Log(LogInformation, "cli")
226 << "Backup file '" << backup << "' already exists. Skipping backup.";
230 Utility::CopyFile(target, backup);
234 chmod(backup.CStr(), 0600);
237 Log(LogInformation, "cli")
238 << "Created backup file '" << backup << "'.";
243 void NodeUtility::SerializeObject(std::ostream& fp, const Dictionary::Ptr& object)
246 ConfigWriter::EmitIdentifier(fp, object->Get("__type"), false);
248 ConfigWriter::EmitValue(fp, 0, object->Get("__name"));
251 ObjectLock olock(object);
252 for (const Dictionary::Pair& kv : object) {
253 if (kv.first == "__type" || kv.first == "__name")
257 ConfigWriter::EmitIdentifier(fp, kv.first, true);
259 ConfigWriter::EmitValue(fp, 1, kv.second);
266 void NodeUtility::UpdateConstant(const String& name, const String& value)
268 String constantsConfPath = NodeUtility::GetConstantsConfPath();
270 Log(LogInformation, "cli")
271 << "Updating '" << name << "' constant in '" << constantsConfPath << "'.";
273 NodeUtility::CreateBackupFile(constantsConfPath);
275 std::ifstream ifp(constantsConfPath.CStr());
277 String tempFile = Utility::CreateTempFile(constantsConfPath + ".XXXXXX", 0644, ofp);
282 while (std::getline(ifp, line)) {
283 if (line.find("const " + name + " = ") != std::string::npos) {
284 ofp << "const " + name + " = \"" + value + "\"\n";
291 ofp << "const " + name + " = \"" + value + "\"\n";
297 _unlink(constantsConfPath.CStr());
300 if (rename(tempFile.CStr(), constantsConfPath.CStr()) < 0) {
301 BOOST_THROW_EXCEPTION(posix_error()
302 << boost::errinfo_api_function("rename")
303 << boost::errinfo_errno(errno)
304 << boost::errinfo_file_name(constantsConfPath));